4、用户认证模块:登录注册功能、JWT令牌机制、权限校验装饰器、Session管理
好,咱们进入考勤系统的核心关卡——用户认证模块。
说实话,这个模块我每次做新项目都会重新审视一遍。为什么?因为认证是系统的第一道门,门没锁好,后面做得再花哨也没用。我在一家创业公司就吃过这个亏,当时赶工期,认证写得潦草,结果上线第三天就被拖库了……嗯,从那以后,我对认证模块的态度就是:宁可多写十行代码,绝不省一个校验。
4.1 登录注册功能:从零搭建用户体系
先聊聊注册。注册说白了就是收集用户信息,存到数据库里。但这里有个关键点——密码绝对不能明文存。
我个人习惯用 werkzeug.security 里的 generate_password_hash 和 check_password_hash。这俩函数用的是 pbkdf2:sha256 算法,加盐哈希,安全级别够用。
from werkzeug.security import generate_password_hash, check_password_hash
# 注册时
hashed_pw = generate_password_hash(request.form['password'])
# 存到数据库:INSERT INTO users (username, password_hash) VALUES (?, ?)
# 登录时
user = query_user_by_username(request.form['username'])
if user and check_password_hash(user.password_hash, request.form['password']):
# 密码正确,放行
else:
# 密码错误,拒绝
避坑指南:我曾经见过有人用 MD5 直接哈希密码,还觉得挺安全。其实 MD5 撞库速度极快,彩虹表一查就破。记住:永远用专门的密码哈希函数,别自己造轮子。
登录的逻辑更简单——验证用户名和密码,通过后生成一个会话标识。但这里有个设计取舍:用 Session 还是用 JWT?
我的建议是:传统 Web 应用用 Session,前后端分离用 JWT。咱们这个 OA 系统是前后端分离的,所以重点讲 JWT。
4.2 JWT令牌机制:无状态认证的利器
JWT 全称 JSON Web Token。说白了,就是一段加密的 JSON 字符串,里面包含了用户身份信息。
它的结构长这样:header.payload.signature。三个部分用点号隔开。
| 部分 | 内容 | 说明 |
|---|---|---|
| Header | {"alg": "HS256", "typ": "JWT"} | 声明算法和类型 |
| Payload | {"sub": "user_id", "exp": 1700000000} | 存放用户数据和过期时间 |
| Signature | HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) | 防止篡改的签名 |
我习惯用 PyJWT 这个库,简单直接。
import jwt
import datetime
SECRET_KEY = "your-secret-key-keep-it-safe"
# 生成令牌
def create_token(user_id):
payload = {
'sub': user_id,
'iat': datetime.datetime.utcnow(),
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=2)
}
return jwt.encode(payload, SECRET_KEY, algorithm='HS256')
# 验证令牌
def verify_token(token):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
return payload['sub']
except jwt.ExpiredSignatureError:
return None # 令牌过期
except jwt.InvalidTokenError:
return None # 无效令牌
注意:JWT 的 Payload 是 Base64 编码,不是加密!千万别把密码、身份证号这类敏感信息放进去。我见过有人把用户手机号直接放 Payload 里,结果前端一解码全暴露了……
为什么用 JWT?因为它无状态。服务器不用存 Session,每次请求客户端带上令牌,服务器解码验证就行。这对分布式部署特别友好——你想想看,如果用了 Session,多台服务器之间还得同步 Session 数据,多麻烦。
4.3 权限校验装饰器:用 Python 装饰器优雅地控制访问
有了令牌,接下来就是权限校验。说白了,就是判断「这个用户能不能访问这个接口」。
Python 的装饰器在这里简直是天作之合。我写了一个 @require_auth 装饰器,每次请求先校验令牌,再校验权限。
from functools import wraps
from flask import request, jsonify
def require_auth(required_role=None):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# 1. 从请求头获取令牌
token = request.headers.get('Authorization')
if not token:
return jsonify({'error': '未提供认证令牌'}), 401
# 2. 验证令牌
user_id = verify_token(token)
if not user_id:
return jsonify({'error': '令牌无效或已过期'}), 401
# 3. 查询用户角色
user = get_user_by_id(user_id)
if required_role and user.role != required_role:
return jsonify({'error': '权限不足'}), 403
# 4. 把用户信息注入到视图函数
return f(user, *args, **kwargs)
return decorated_function
return decorator
# 使用示例
@app.route('/api/attendance/report')
@require_auth(required_role='admin')
def get_attendance_report(current_user):
# 只有管理员才能访问
return jsonify({'report': generate_report()})
我的经验:权限校验最好做成可配置的。我曾经在一个项目里把角色和权限写死在装饰器里,结果后来加了十几个角色,改得我头皮发麻。现在我会把权限规则放到数据库里,装饰器只负责查表判断。
4.4 Session管理:传统方案与JWT的对比
虽然咱们用了 JWT,但 Session 管理还是得聊。毕竟很多老项目还在用 Session,你可能会遇到。
Session 的原理很简单:用户登录后,服务器生成一个 Session ID,存到 Cookie 里发给客户端。客户端每次请求带上 Cookie,服务器根据 Session ID 找到对应的用户数据。
| 对比项 | Session | JWT |
|---|---|---|
| 存储位置 | 服务器内存/Redis | 客户端 |
| 扩展性 | 需要共享 Session 存储 | 天然支持分布式 |
| 安全性 | 服务器控制,可主动销毁 | 过期前无法撤销 |
| 性能 | 每次请求查存储 | 解码即可,更快 |
你可能会问:那 JWT 过期前想踢人下线怎么办?
嗯,这是个好问题。我的做法是维护一个黑名单。用户登出时,把当前令牌的 jti(JWT ID)加入 Redis 黑名单,设置过期时间等于令牌剩余有效期。每次校验时先查黑名单。
# 登出时
def logout(token):
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'], options={"verify_exp": False})
jti = payload['jti']
# 计算剩余过期时间
remaining = payload['exp'] - int(time.time())
redis.setex(f"blacklist:{jti}", remaining, "1")
# 校验时
def verify_token(token):
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
if redis.exists(f"blacklist:{payload['jti']}"):
return None # 令牌已被撤销
return payload['sub']
重要提醒:JWT 的 Secret Key 一定要保管好!我见过有人把 Secret Key 硬编码在代码里,还提交到了 GitHub……结果被爬虫扫到,整个系统的令牌都能伪造。建议用环境变量加载,或者用专门的密钥管理服务。
好了,用户认证模块的核心内容就这些。总结一下:
- 注册:密码必须哈希存储,用
generate_password_hash - 登录:生成 JWT 令牌,设置合理的过期时间
- 权限校验:用装饰器统一处理,支持角色判断
- Session 管理:JWT 无状态,但需要黑名单机制处理登出
下一章咱们开始写考勤打卡的核心逻辑。到时候你会看到,认证模块写得稳,后面的业务逻辑才能写得爽。咱们下章见。