📜  没有数据库的烧瓶身份验证用户 - Python (1)

📅  最后修改于: 2023-12-03 14:56:02.683000             🧑  作者: Mango

没有数据库的烧瓶身份验证用户 - Python

本文将介绍如何用 Python 开发一种基于烧瓶(Flask)的身份验证系统,而不必使用数据库存储用户信息。该系统使用基于文件的用户存储系统,它可以轻松地扩展到存储更多用户,而且运行速度相当快。

实现思路

身份验证系统是一个常见的功能,它通常涉及到创建用户,登录和鉴权等问题。在传统的身份验证系统中,通常使用关系型数据库存储用户信息。但是,考虑到简单性,我们将使用基于文件的用户存储系统代替数据库。

具体实现思路如下:

  1. 创建用户账户时,将用户名和密码散列以后,保存在一个文件中;
  2. 用户登录时,在用户账户文件中查找匹配用户名的记录,比对密码散列值,验证用户身份;
  3. 如果身份验证成功,将为用户返回一个 token,该 token 用于后续访问保护的资源时的身份验证;
主要功能

本身份验证系统实现了以下功能:

  • 用户账户的创建
  • 用户身份验证
  • 保护资源的访问,只有具有有效 token 的用户才能访问
项目结构
.
├── app.py  # Flask 应用程序
└── users/  # 存储用户信息的目录
    └── alice  # 用户 Alice 的信息
        └── password  # Alice 的密码散列值
Flask 应用程序

首先,我们需要创建一个 Flask 应用程序,并添加需要的路由。

from flask import Flask, jsonify, request
from flask_jwt import JWT, jwt_required, current_identity
from werkzeug.security import safe_str_cmp

app = Flask(__name__)
app.config['SECRET_KEY'] = 'super-secret'
jwt = JWT(app, lambda username: identity(username))


class User:
    def __init__(self, id, username, password):
        self.id = id
        self.username = username
        self.password = password

    def __str__(self):
        return f'User(id={self.id}, username={self.username})'


def identity(payload):
    username = payload['identity']
    user = get_user(username)
    return user


def get_user(username):
    user_file = f'users/{username}/password'
    try:
        with open(user_file, 'r') as f:
            password_hash = f.readline().strip()
            return User(1, username, password_hash)
    except FileNotFoundError:
        return None


@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username', None)
    password = request.json.get('password', None)

    user = get_user(username)
    if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')):
        token = jwt.jwt_encode_callback(user)
        return jsonify({'token': token.decode('utf-8')})
    else:
        return jsonify({'error': 'Invalid username or password'}), 401


@app.route('/protected')
@jwt_required()
def protected():
    return jsonify({'message': f'Hello, {current_identity.username}!'})

在创建 Flask 应用程序时,我们指定了一个秘密 key。该 key 用于生成 token,也可以用于加密会话 cookie。

我们使用 Flask-JWT 扩展来实现 token 鉴权。该扩展提供了一个装饰器 jwt_required,用于保护需要身份验证的路由。

get_user 函数将会根据用户名返回用户对象,如果找不到指定用户名的用户,则该函数返回 None。

在登录路由中,我们首先从请求中获取用户名和密码。如果存在指定用户名的用户,且密码正确,则生成一个 token 并返回。否则,返回错误消息。

在访问保护资源的路由中,我们使用 jwt_required 装饰器,以确保只有携带有效 token 的用户才能访问该路由。如果用户的身份验证通过,我们将返回一个消息,其中包含登录用户的用户名信息。

用户账户的创建

在实现用户账户的创建时,需要将用户的信息散列化以后存储到文件中。

import hashlib
import os

def add_user(username, password):
    password_hash = hashlib.sha256(password.encode('utf-8')).hexdigest()

    user_dir = f'users/{username}'
    if not os.path.exists(user_dir):
        os.makedirs(user_dir)

    password_file = f'{user_dir}/password'
    with open(password_file, 'w') as f:
        f.write(password_hash)

在添加新用户时,我们首先根据用户提供的密码计算出密码散列值,并将该值存储到文件中。为了尽可能地增强对抗密码破解的能力,我们使用了 SHA256 算法来计算密码散列值。

运行示例

我们可以通过以下代码来使用 Flask 内置的 Web 服务器运行我们的应用程序:

if __name__ == '__main__':
    app.run(debug=True)

该参数会启动debug模式,以便在代码中出现错误时,能准确地获取错误信息。

在测试该应用程序时,我们需要使用 HTTPie 或 Postman 等 HTTP 客户端,以向应用程序发送请求并获取响应。首先,我们需要使用用户名和密码创建一个新的用户:

http POST localhost:5000/users username=alice password=secret

该请求将会发送一个 POST 请求,参数为 username 和 password,依据响应是否 200 来判断是否创建成功;

接下来,我们可以使用该用户的用户名和密码来登录并获取 token:

http POST localhost:5000/login username=alice password=secret

该请求将会发送一个 POST 请求,参数为 username 和 password,响应为 token;

最后,我们可以使用该 token 来访问保护资源:

http GET localhost:5000/protected Authorization:Bearer ${TOKEN}

该请求将会发送一个 GET 请求,参数为 Authorization,用于获取保护资源的token;

如果 token 有效,则将收到一个与以下类似的响应:

{
    "message": "Hello, alice!"
}
总结

在本文中,我们介绍了如何使用 Flask 和基于文件的用户存储系统来实现一种简单的身份验证系统。该系统具有良好的扩展性和运行速度。在实践中,我们可以根据具体的业务需求以及安全需求,对该系统进行进一步的定制和优化。