在Python Flask框架中实现用户登录功能,采用JWT(JSON Web Token)令牌的身份验证机制是主流方案,它不需要在服务端存储用户会话状态,适合前后端分离、分布式部署的场景。JWT令牌由头部、载荷、签名三部分组成,服务端通过密钥生成令牌返回给客户端,客户端后续请求携带令牌即可完成身份验证。

环境准备与依赖安装
首先需要安装Flask框架以及处理JWT的相关依赖库,执行以下命令完成安装:
pip install flask pyjwt werkzeug
其中pyjwt用于生成和校验JWT令牌,werkzeug提供了密码哈希处理的方法,保障用户密码存储的安全性。
JWT工具类封装
为了方便后续生成和校验令牌,我们先封装一个JWT工具类,统一管理密钥、令牌有效期等配置:
import jwt
from datetime import datetime, timedelta
class JWTUtil:
# 密钥,实际项目中建议放在环境变量中,不要硬编码
SECRET_KEY = "your_secret_key_here"
# 令牌有效期,这里设置为1小时
EXPIRE_DELTA = timedelta(hours=1)
@staticmethod
def generate_token(user_id):
"""生成JWT令牌"""
payload = {
"user_id": user_id,
"exp": datetime.utcnow() + JWTUtil.EXPIRE_DELTA,
"iat": datetime.utcnow()
}
token = jwt.encode(payload, JWTUtil.SECRET_KEY, algorithm="HS256")
return token
@staticmethod
def verify_token(token):
"""校验JWT令牌,返回用户ID,校验失败返回None"""
try:
payload = jwt.decode(token, JWTUtil.SECRET_KEY, algorithms=["HS256"])
return payload.get("user_id")
except jwt.ExpiredSignatureError:
# 令牌过期
return None
except jwt.InvalidTokenError:
# 令牌无效
return None
用户模型与登录接口实现
假设我们已经有一个简单的用户数据模型,这里用内存字典模拟用户数据库,实际项目中可以替换为数据库查询:
from werkzeug.security import generate_password_hash, check_password_hash
# 模拟用户数据库,存储用户名、密码哈希、用户ID
users = {
"test_user": {
"id": 1,
"password_hash": generate_password_hash("test_password")
}
}
接下来实现登录接口,接收用户名和密码,校验通过后返回JWT令牌:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/login", methods=["POST"])
def login():
data = request.get_json()
username = data.get("username")
password = data.get("password")
if not username or not password:
return jsonify({"msg": "用户名和密码不能为空"}), 400
user = users.get(username)
if not user:
return jsonify({"msg": "用户不存在"}), 404
if not check_password_hash(user["password_hash"], password):
return jsonify({"msg": "密码错误"}), 401
# 生成JWT令牌
token = JWTUtil.generate_token(user["id"])
return jsonify({
"msg": "登录成功",
"token": token
}), 200
令牌校验装饰器实现
为了统一处理需要登录才能访问的接口,我们可以实现一个装饰器,自动校验请求头中的JWT令牌:
from functools import wraps
def login_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 从请求头中获取Authorization字段,格式为Bearer <token>
auth_header = request.headers.get("Authorization")
if not auth_header:
return jsonify({"msg": "缺少身份验证令牌"}), 401
parts = auth_header.split()
if len(parts) != 2 or parts[0].lower() != "bearer":
return jsonify({"msg": "令牌格式错误"}), 401
token = parts[1]
user_id = JWTUtil.verify_token(token)
if not user_id:
return jsonify({"msg": "令牌无效或已过期"}), 401
# 将用户ID存入request对象,方便后续接口使用
request.user_id = user_id
return func(*args, **kwargs)
return wrapper
受保护接口示例
编写一个需要登录才能访问的接口,测试令牌校验功能:
@app.route("/user/info", methods=["GET"])
@login_required
def get_user_info():
# 这里可以通过request.user_id查询用户详细信息,这里简单返回用户ID
return jsonify({
"msg": "获取用户信息成功",
"user_id": request.user_id
}), 200
功能测试
启动Flask服务后,可以通过接口测试工具验证功能:
- 调用
/login接口,传入正确的用户名和密码,会返回JWT令牌 - 调用
/user/info接口时,在请求头中添加Authorization: Bearer <获取的令牌>,即可正常返回用户信息 - 如果令牌过期或者格式错误,接口会返回对应的错误提示
注意事项
在实际项目中使用JWT实现用户登录时,需要注意以下几点:
- JWT密钥需要妥善保管,不要泄露,建议通过环境变量配置,不要硬编码在代码中
- 令牌有效期不宜设置过长,避免令牌泄露后造成安全风险,可以配合刷新令牌机制实现长期登录
- 不要在JWT载荷中存储敏感信息,因为载荷部分是可以被解码的,只是签名保证了令牌没有被篡改
- 如果需要实现用户登出功能,可以在服务端维护一个令牌黑名单,校验令牌时同时检查是否在黑名单中