4、用户认证模块:登录注册功能、JWT令牌机制、权限校验装饰器、Session管理

好,咱们进入考勤系统的核心关卡——用户认证模块。

说实话,这个模块我每次做新项目都会重新审视一遍。为什么?因为认证是系统的第一道门,门没锁好,后面做得再花哨也没用。我在一家创业公司就吃过这个亏,当时赶工期,认证写得潦草,结果上线第三天就被拖库了……嗯,从那以后,我对认证模块的态度就是:宁可多写十行代码,绝不省一个校验

4.1 登录注册功能:从零搭建用户体系

先聊聊注册。注册说白了就是收集用户信息,存到数据库里。但这里有个关键点——密码绝对不能明文存

我个人习惯用 werkzeug.security 里的 generate_password_hashcheck_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 无状态,但需要黑名单机制处理登出

下一章咱们开始写考勤打卡的核心逻辑。到时候你会看到,认证模块写得稳,后面的业务逻辑才能写得爽。咱们下章见。