使用适用于 PostgreSQL 和 Redis 的 Vultr 托管数据库对 Python 应用程序进行身份验证

介绍

身份验证是在授予对应用程序的访问权限之前验证用户凭据的过程。 要登录应用程序,最终用户需要输入他们的用户名和密码。 在幕后,后台进程将用户的凭据与数据库值进行比较,以检查是否存在匹配项。

每次用户访问应用程序时,整个身份验证过程都需要往返于基于磁盘的数据库(如 PostgreSQL)。 当应用程序的用户群增长时,基于磁盘的数据库会遇到可伸缩性问题。 为了克服挑战,这就是像 Redis 这样的内存数据库发挥作用的地方。

当用户首次登录应用程序时,您可以使用 Redis 数据库缓存身份验证详细信息。 然后,在以下请求期间,您可以查询 Redis 服务器以检查身份验证状态,而不是访问基于磁盘的数据库。 Redis 比基于磁盘的数据库快几倍。 这种方法最终使您的应用程序更快,更具可扩展性。

本指南描述了使用来自 Vultr 平台的托管 PostgreSQL 和 Redis 数据库对 Python 应用程序进行身份验证的过程。 Vultr 提供了一个安全且高度可扩展的托管数据库,开箱即用,可以自动执行数据库管理的所有困难任务。

先决条件

要遵循本指南:

  • 部署 Ubuntu 20.04 服务器。

  • 创建非root sudo 用户。

  • 提供一个 PostgreSQL 和一个 Redis 托管数据库集群。 对两个集群使用相同的位置。

  • 找到 连接细节 对于下面的每个数据库 概述 标签。 本指南使用以下示例连接详细信息:

    • 雷迪斯服务器:

      • 用户名: default

      • 密码: EXAMPLE_REDIS_PASSWORD

      • 主持人: SAMPLE_REDIS_DB_HOST_STRING.vultrdb.com

      • 港口: 16752

    • PostgreSQL 服务器:

      • 用户名: vultradmin

      • 密码: EXAMPLE_POSTGRESQL_PASSWORD

      • 主持人: SAMPLE_POSTGRESQL_DB_HOST_STRING.vultrdb.com

      • 港口: 16751

1. 建立样本数据库

本指南使用托管 PostgreSQL 数据库将数据永久存储在磁盘上。 对于这个示例应用程序,您需要一个数据库和两个表。 第一个表存储产品。 然后,当用户向应用程序发送请求时,Python 脚本会查询该表以返回 JSON 格式的产品。 第二个表存储用户及其身份验证凭据。 按照以下步骤设置数据库:

  1. 更新包信息索引。

    $ sudo apt update
    
  2. 安装 postgresql-client 包裹。 因为此应用程序使用来自 Vultr 的 PostgreSQL 管理的数据库,所以您只需要 PostgreSQL 命令行客户端来查询数据库。

    $ sudo apt install -y postgresql-client
    
  3. 使用 psql 命令登录到托管的 PostgreSQL 数据库。 代替 SAMPLE_POSTGRESQL_DB_HOST_STRING.vultrdb.com 用正确的名字 host.

    $ psql -h SAMPLE_POSTGRESQL_DB_HOST_STRING.vultrdb.com -p 16751 -U vultradmin defaultdb
    
  4. 确保您收到以下密码提示。

    Password for user vultradmin:
    
  5. Enter 托管 PostgreSQL 用户的密码,然后按 ENTER 继续。 然后,验证以下输出。

    defaultdb=>
    
  6. Enter 以下命令创建示例 my_company 数据库。

    defaultdb=> CREATE DATABASE my_company;
    

    输出。

    CREATE DATABASE
    
  7. 切换到新的 my_company 数据库。

    defaultdb=> c my_company;
    

    输出。

    You are now connected to database "my_company" as user "vultradmin".
    
    
    
    my_company=>
    
  8. 创建一个 products 桌子。 本指南使用单个表。 在生产环境中,您可能有数十个或数百个表,具体取决于应用程序的复杂性。

    my_company=> CREATE TABLE products (
    
                     product_id SERIAL PRIMARY KEY,
    
                     product_name VARCHAR (50),
    
                     retail_price  NUMERIC(5, 2)          
    
                 );
    

    输出。

    CREATE TABLE
    
  9. 填充 products 桌子。

    my_company=> INSERT INTO products (product_name, retail_price) VALUES ('1L FOUNTAIN DRINKING WATER', 2.55);
    
                 INSERT INTO products (product_name, retail_price) VALUES ('PINK COTTON BUDS', 4.85);
    
                 INSERT INTO products (product_name, retail_price) VALUES ('WINE GLASS', 9.75);
    

    输出。

    ...
    
    INSERT 0 1
    
  10. 查询 products 表以确保数据到位。

    my_company=> SELECT
    
                     product_id,
    
                     product_name,
    
                     retail_price
    
                 FROM products;
    

    输出。

     product_id |        product_name        | retail_price
    
    ------------+----------------------------+--------------
    
              1 | 1L FOUNTAIN DRINKING WATER |         2.55
    
              2 | PINK COTTON BUDS           |         4.85
    
              3 | WINE GLASS                 |         9.75
    
    (3 rows)
    
  11. 创建一个 users 桌子。 这 users 表存储用户的信息,例如 user_id, username, 和 pwd (密码)。

    my_company=> CREATE TABLE users (
    
                     user_id SERIAL PRIMARY KEY,
    
                     username VARCHAR (50),
    
                     pwd VARCHAR (255) 
    
                 );
    

    输出。

    CREATE TABLE
    
  12. 发出以下命令以启用 pgcrypto 扩大。 在将密码插入到 users 桌子。

    my_company=> CREATE EXTENSION pgcrypto;
    

    输出。

    CREATE EXTENSION
    
  13. 填充 users 带有样本数据的表格。 本指南使用 EXAMPLE_PASSWORDEXAMPLE_PASSWORD_2. 请记住使用强密码来防止生产环境中的暴力攻击。

    my_company=> INSERT INTO users (username, pwd) VALUES ('john_doe', crypt('EXAMPLE_PASSWORD', gen_salt('bf')));
    
                 INSERT INTO users (username, pwd) VALUES ('mary_smith', crypt('EXAMPLE_PASSWORD_2', gen_salt('bf')));
    

    输出。

    ...
    
    INSERT 0 1
    
  14. 查询 users 核实记录和工作情况的表格 pgcrypto 扩大。

    my_company=> SELECT
    
                     user_id,
    
                     username,
    
                     pwd
    
                 FROM users;
    

    输出。

     user_id |  username  |                             pwd
    
    ---------+------------+--------------------------------------------------------------
    
           1 | john_doe   | $2a$06$spijfwl34nCdBpApp1C68OWa//j0buReiQ4SHAJVCV4sm627iyyZW
    
           2 | mary_smith | $2a$06$g6FjH7PXSCMT75uIKB94ZOUWHbeth0SsHebOqcykjXM4Dq6mtlxtG
    
    (2 rows) 
    
  15. 从受管 PostgreSQL 服务器注销。

    my_company=>  q
    
  16. 继续下一步,为 PostgreSQL 服务器创建数据库类。

2. 创建 PostgreSQL 数据库类

此步骤向您展示怎样创建一个中央 PostgreSQL 类,您可以在应用程序中使用该类来访问数据库功能。 按照以下步骤创建类:

  1. 创建一个 project 用于将源代码与系统文件分开的目录。

    $ mkdir project
    
  2. 切换到新的 project 目录。

    $ cd project
    
  3. 开一个新的 posgresql_gateway.py 文本编辑器中的文件。

    $ nano postgresql_gateway.py
    
  4. Enter 将以下信息放入 postgresql_gateway.py 文件。 更换 db_passdb_host 具有正确值 hostpassword 对于托管的 PostgreSQL 数据库。

    import psycopg2
    
    import bcrypt
    
    
    
    class PostgresqlGateway:
    
    
    
        def __init__(self):
    
    
    
            db_host="SAMPLE_POSTGRESQL_DB_HOST_STRING.vultrdb.com"    
    
            db_port = 16751
    
            db_name="my_company" 
    
            db_user="vultradmin"
    
            db_pass="EXAMPLE_POSTGRESQL_PASSWORD"   
    
    
    
            self.postgresql_client = psycopg2.connect(host = db_host, database = db_name, user = db_user, password = db_pass, port = db_port)
    
    
    
        def get_products(self):
    
    
    
            sql_string = 'select product_id, product_name, retail_price from products'
    
    
    
            cur = self.postgresql_client.cursor()
    
    
    
            cur.execute(sql_string)
    
            rows = cur.fetchall()
    
    
    
            products = []
    
            dt_columns = list(cur.description)
    
    
    
            for row in rows:
    
    
    
                row_data = {}
    
    
    
                for i, col in enumerate(dt_columns):
    
    
    
                    row_data[col.name] = str(row[i])
    
    
    
                products.append(row_data)
    
    
    
            return products
    
    
    
        def authenticate_user(self, username, password):
    
    
    
            sql_string = "select username, pwd from users where username =  %s"
    
    
    
            cur = self.postgresql_client.cursor()
    
    
    
            cur.execute(sql_string, (username,))                
    
    
    
            if cur.rowcount < 1 :     
    
    
    
                return False
    
    
    
            else:
    
    
    
                row = cur.fetchone()
    
    
    
                if bcrypt.checkpw(password.encode('utf8'), row[1].encode('utf8')):
    
    
    
                    self.hashed_password = row[1].encode('utf8')
    
    
    
                    return True
    
    
    
                else:  
    
    
    
                    return False
    
  5. Save 和 close 这 postgresql_gateway.py 文件。

postgresql_gateway.py 文件解释:

  1. import 部分声明了两个库。 这 psycopg2 是用于 PostgreSQL 数据库的流行 Python 库。 这 bcrypt 是一个密码哈希库。

    import psycopg2
    
    import bcrypt
    
    ...
    
  2. PostgresqlGateway 类具有三个方法。

    class PostgresqlGateway:
    
    
    
        def __init__(self):
    
    
    
            ...
    
    
    
        def get_products(self):
    
    
    
            ...
    
    
    
        def authenticate_user(self, username, password):
    
    
    
            ...
    
  3. _init_() 方法在实例化类时建立到 PostgreSQL 数据库的数据库连接。

  4. get_products(...) 方法查询 products 表以从数据库中检索产品列表。

  5. authenticate_user(...) 方法查询 users 表以在用户尝试登录应用程序时查找匹配项。 如果用户的凭据与中的记录匹配 users 表, authenticate_user 方法返回 True.

  6. if bcrypt.checkpw(password.encode('utf8'), row[1].encode('utf8')): 语句将用户密码与数据库值进行比较 bcrypt 图书馆。

postgresql_gateway.py 类现在准备好了。 要在其他 Python 文件中使用它,请使用以下语法:

    import postgresql_gateway        

    pg = postgresql_gateway.PostgresqlGateway() 

    ... = pg.get_products()

    ... = pg.authenticate_user(username, password)

按照下一步创建 Redis 数据库类。

3.创建Redis数据库类

此步骤重点创建 Redis 数据库类。 该类提供用于创建和检索密钥的 Redis 功能。 执行以下步骤创建类:

  1. 开一个新的 redis_gateway.py 文本编辑器中的文件。

    $ nano redis_gateway.py
    
  2. Enter 将以下信息放入 redis_gateway.py 文件。 更换 db_hostdb_pass 具有正确值 hostpassword 从您的托管 Redis 服务器。

    import redis
    
    import bcrypt
    
    
    
    class RedisGateway:
    
    
    
        def __init__(self):
    
    
    
            db_host="SAMPLE_REDIS_DB_HOST_STRING.vultrdb.com"
    
            db_port = 16752
    
            db_pass="EXAMPLE_REDIS_PASSWORD"                             
    
    
    
            self.redis_client = redis.Redis(host = db_host, port = db_port, password = db_pass, ssl="true")
    
    
    
        def cache_user(self, username, password):            
    
    
    
            self.redis_client.set(username, password)               
    
    
    
        def authenticate_user(self, username, password):
    
    
    
            if self.redis_client.exists(username):  
    
    
    
                hashed_password = self.redis_client.get(username)
    
    
    
                if bcrypt.checkpw(password.encode('utf8'), hashed_password):
    
    
    
                    return True
    
            else:
    
    
    
                return False
    
  3. Save 和 close 这 redis_gateway.py 文件。

redis_gateway.py 文件解释:

  1. import 部分声明了两个 Python 库。 这 redis 库提供了 Python 和托管的 Redis 服务器之间的接口。 这 bcrypt 库比较用户提供的纯文本密码和来自 Redis 的散列密码。

    ...
    
    import redis
    
    import bcrypt
    
  2. RedisGateway 类具有三个方法。

    ...
    
    class RedisGateway:
    
    
    
        def __init__(self):
    
    
    
            ...
    
    
    
        def cache_user(self, username, password):            
    
    
    
            ...       
    
    
    
        def authenticate_user(self, username, password):
    
    
    
            ...
    
  3. _init_() 方法建立与托管 Redis 数据库的连接。

  4. cache_user() 方法使用 self.redis_client.set(username, password) 功能。 每个用户都有一个独一无二的 username 充当 Redis 键,而 password 是一个 Redis 值。

  5. authenticate_user(...) 方法查询 Redis 服务器以检查键 (hashed_password) 以给定命名 username 存在使用 if self.redis_client.exists(username): 陈述。 如果用户的密码可从 Redis 服务器获得,则 authenticate_user(...) 函数返回 True. 否则,函数返回 False.

RedisGateway 类现在准备好了。 您可以使用以下语法在其他 Python 文件中导入和使用该类:

    import redis_gateway 

    rg = redis_gateway.RedisGateway() 

    ... = pg.authenticate_user(username, password)

    rg.cache_user(username, pg.hashed_password)

按照下一步完成应用程序的编码。

4. 创建应用程序的入口点

最后一步是创建示例应用程序的入口点。 本指南使用 main.py 文件作为应用程序的启动文件。 按照以下步骤创建文件:

  1. 开一个新的 main.py 文本编辑器中的文件。

    $ nano main.py
    
  2. Enter 将以下信息放入 main.py 文件。

    import http.server
    
    from http import HTTPStatus        
    
    import socketserver
    
    
    
    import json
    
    import base64
    
    
    
    import postgresql_gateway        
    
    import redis_gateway 
    
    
    
    class httpHandler(http.server.SimpleHTTPRequestHandler):
    
    
    
                def do_GET(self):
    
    
    
                    authHeader = self.headers.get('Authorization').split(' ');
    
                    username, password = base64.b64decode(authHeader[1]).decode('utf8').split(':')
    
    
    
                    self.send_response(HTTPStatus.OK)
    
                    self.send_header('Content-type', 'application/json')
    
                    self.end_headers()
    
    
    
                    pg = postgresql_gateway.PostgresqlGateway() 
    
                    rg = redis_gateway.RedisGateway() 
    
    
    
                    data = dict()
    
    
    
                    if rg.authenticate_user(username, password) == True:
    
    
    
                        products = pg.get_products()
    
    
    
                        data = {'authenticated_by' : 'Redis Server', 'data': products}
    
    
    
                    else:   
    
    
    
                        if pg.authenticate_user(username, password) == True:                     
    
    
    
                            rg.cache_user(username, pg.hashed_password)
    
    
    
                            products  = pg.get_products()   
    
    
    
                            data = {'authenticated_by' : 'PostgreSQL Server', 'data': products}                    
    
    
    
                        else:                     
    
    
    
                            data = {'error': 'Authentication failed.'}
    
    
    
                    resp = json.dumps(data, indent = 4, separators = (',', ': '))               
    
    
    
                    self.wfile.write(bytes(resp + 'rn', "utf8")) 
    
    
    
    httpServer = socketserver.TCPServer(('', 8080), httpHandler)
    
    
    
    print("HTTP server started at port 8080...")
    
    
    
    try:
    
    
    
        httpServer.serve_forever()
    
    
    
    except KeyboardInterrupt:
    
    
    
        httpServer.server_close()
    
        print("The server is stopped.")
    
  3. Save 和 close 这 main.py 文件。

main.py 文件解释:

  1. import 部分声明 HTTP 服务器 (http.server, HTTPStatus, 和 socketserver), json, base64, postgresql_gateway, 和 redis_gateway 图书馆。

    import http.server
    
    from http import HTTPStatus        
    
    import socketserver
    
    
    
    import json
    
    import base64
    
    
    
    import postgresql_gateway        
    
    import redis_gateway 
    
    ...
    
  2. httpHandler 是一个应用程序的 HTTP 处理程序类 do_GET(self) 方法。 当用户发送一个 GET 向应用程序请求。 这 do_GET 方法输出 JSON 输出。

    class httpHandler(http.server.SimpleHTTPRequestHandler):
    
    
    
                def do_GET(self):
    
                    ...
    
                    resp = json.dumps(data, indent = 4, separators = (',', ': ')) 
    
                    self.wfile.write(bytes(resp + 'rn', "utf8")) 
    
  3. do_GET() 方法声明您之前使用以下语法创建的两个自定义 PostgreSQL 和 Redis 库。

              pg = postgresql_gateway.PostgresqlGateway() 
    
              rg = redis_gateway.RedisGateway() 
    
  4. 该应用程序的主要逻辑在于以下代码。

       ...
    
    
    
       if rg.authenticate_user(username, password) == True:
    
    
    
                        products = pg.get_products()
    
    
    
                        data = {'authenticated_by' : 'Redis Server', 'data': products}
    
    
    
                    else:   
    
    
    
                        if pg.authenticate_user(username, password) == True:                     
    
    
    
                            rg.cache_user(username, pg.hashed_password)
    
    
    
                            products  = pg.get_products()   
    
    
    
                            data = {'authenticated_by' : 'PostgreSQL Server', 'data': products}                    
    
    
    
                        else:                     
    
    
    
                            data = {'error': 'Authentication failed.'}
    
    
    
       ...
    
  5. rg.authenticate_user(username, password) == True: 逻辑查询Redis服务器检查用户的详细信息是否已经缓存。 如果函数返回 True,逻辑调用 products = pg.get_products() 从 PostgreSQL 数据库输出产品。

  6. 如果在 Redis 服务器上找不到用户的详细信息,则 if pg.authenticate_user(username, password) == True: 逻辑从 PostgreSQL 数据库中查找用户的凭据。 如果用户详细信息正确,则逻辑调用 rg.cache_user(username, pg.hashed_password) 将用户的详细信息缓存到 Redis 服务器以供其他调用,然后运行 pg.get_products() 函数从 PostgreSQL 数据库输出产品。

  7. 声明 {'authenticated_by' : 'Redis Server', 'data': products}{'authenticated_by' : 'PostgreSQL Server', 'data': products} 允许您确定用户怎样向应用程序进行身份验证。 这仅用于演示目的,您可以删除 authenticated_by 生产环境中的值。

  8. 下面的语句启动一个 web 服务器,它监听端口上的传入连接 8080 并宣布 httpHandler 充当处理函数。

    ...
    
    httpServer = socketserver.TCPServer(('', 8080), httpHandler)
    
    
    
    print("HTTP server started at port 8080...")
    
    
    
    try:
    
    
    
        httpServer.serve_forever()
    
    
    
    except KeyboardInterrupt:
    
    
    
        httpServer.server_close()
    
        print("The server is stopped.")
    

您的应用程序现在可以进行测试了。

5. 测试应用逻辑

最后一步是安装应用程序所需的所有第三方库并测试身份验证逻辑。 请按照以下步骤完成这些步骤:

  1. 安装 Python pip 包裹。

    $ sudo apt install -y python3-pip
    
  2. 使用 pip 安装包 psycopg2 模块。 对于测试和开发,使用二进制包(psycopg2-binary). 但是,在生产环境中,请考虑使用 psycopg2 包裹。

    $ pip install psycopg2-binary
    

    输出。

    ...
    
    Successfully installed psycopg2-binary-2.9.5
    
  3. 安装 redis Python 模块。

    $ pip install redis
    

    输出。

    ...
    
    Successfully installed async-timeout-4.0.2 packaging-21.3 pyparsing-3.0.9 redis-4.3.5
    
  4. 安装 bcrypt Python 模块。

    $ pip install bcrypt
    

    输出。

    ...
    
    Successfully installed bcrypt-4.0.1
    
  5. 使用 python3 命令运行应用程序。

    $ python3 main.py
    

    输出。

    HTTP server started at port 8080...
    
  6. 与您的服务器建立另一个 SSH 连接并发出以下 Linux curl 命令发送两个 GET 对应用程序的请求。

    • john_doe:

      $ curl -X GET -u john_doe:EXAMPLE_PASSWORD  https://localhost:8080/
      
      $ curl -X GET -u john_doe:EXAMPLE_PASSWORD  https://localhost:8080/
      
    • mary_smith:

      $ curl -X GET -u marysmith:EXAMPLEPASSWORD_2 https://localhost:8080/

      $ curl -X GET -u marysmith:EXAMPLEPASSWORD_2 https://localhost:8080/

  7. 请注意以下输出。 在第一个输出中, authenticated_by 值读取 PostgreSQL Server. 但是,在第二个请求中, authenticated_by 值读取 Redis Server.

    输出 1。

    ...
    
    {
    
        "authenticated_by": "PostgreSQL Server",
    
        "data": [
    
            {
    
                "product_id": "1",
    
                "product_name": "1L FOUNTAIN DRINKING WATER",
    
                "retail_price": "2.55"
    
            },
    
            {
    
                "product_id": "2",
    
                "product_name": "PINK COTTON BUDS",
    
                "retail_price": "4.85"
    
            },
    
            {
    
                "product_id": "3",
    
                "product_name": "WINE GLASS",
    
                "retail_price": "9.75"
    
            }
    
        ]
    
    }
    

    输出 2。

    ...
    
    
    
    {
    
        "authenticated_by": "Redis Server",
    
        "data": [
    
            {
    
                "product_id": "1",
    
                "product_name": "1L FOUNTAIN DRINKING WATER",
    
                "retail_price": "2.55"
    
            },
    
            {
    
                "product_id": "2",
    
                "product_name": "PINK COTTON BUDS",
    
                "retail_price": "4.85"
    
            },
    
            {
    
                "product_id": "3",
    
                "product_name": "WINE GLASS",
    
                "retail_price": "9.75"
    
            }
    
        ]
    
    }
    

您的应用程序逻辑按预期工作。

结论

本指南使用 Vultr 的托管 Redis 和 PostgreSQL 数据库来加速在 Ubuntu 20.04 服务器上验证 Python 应用程序。 使用本指南的示例源代码文件在您的下一个 Python 项目中扩展您的应用程序。

通过以下链接阅读有关 Redis 服务器的更多指南:

  • 使用 Vultr 托管的 Redis 数据库在 Python 中实现购物车。

  • 怎样在 Go、NodeJS、PHP、Python 和 redis-cli 中使用 TLS/SSL 安全连接到 Redis。

  • 怎样在 Golang 中使用 Redis 缓存和 PostgreSQL

文章标题 名称(可选) 电子邮件(可选) 描述

发送建议

注:本教程在Vultr VPS上测试通过,如需部署请前往Vultr.com