Skip to content

本章概要

登录 -> Token 获取 -> 权限校验 -> Token 刷新 -> 退出登录的完整认证链路实战说明。

认证流程实战

本章将登录 -> Token 获取 -> 权限校验 -> Token 刷新 -> 退出登录的完整认证链路串联说明,帮助开发者理解端到端的认证机制。

认证架构总览

+----------+     1获取验证码      +----------+
|          | -----------------> |          |
|  前端    |                     |  后端    |
|  Vue3    |  2登录(账号+密码+验证码) |  Django  |
|          | -----------------> |          |
|          |  3返回 Token        |          |
|          | <------------------ |          |
|          |                     |          |
|          |  4携带 Token 请求    |          |
|          | -----------------> |          |
|          |  5返回业务数据       |          |
|          | <------------------ |          |
|          |                     |          |
|          |  6刷新 Token        |          |
|          | -----------------> |          |
|          |  7返回新 Token       |          |
|          | <------------------ |          |
|          |                     |          |
|          |  8退出登录           |          |
|          | -----------------> |          |
+----------+                     +----------+

第 1 步:获取验证码

登录前需先获取图形验证码,防止暴力破解。

请求

GET /login/captcha

响应

json
{
    "code": 0,
    "msg": "操作成功",
    "data": {
        "captcha": "data:image/png;base64,iVBORw0KGgo...",
        "key": 1
    },
    "ok": true
}
  • captcha:Base64 编码的验证码图片,前端直接赋值给 <img>src
  • key:验证码记录 ID,登录时需回传

后端实现

python
# application/login/views.py
from captcha.models import CaptchaStore
from captcha.views import captcha_image

class CaptchaView(View):
    def get(self, request):
        # 生成验证码密钥
        hashkey = CaptchaStore.generate_key()
        id_key = CaptchaStore.objects.filter(hashkey=hashkey).first().id

        # 生成验证码图片并转 Base64
        image = captcha_image(request, hashkey)
        base64_image = base64.b64encode(image.content)
        img_str = "data:image/png;base64," + base64_image.decode("utf-8")

        return R.ok(idkey=id_key, data={"captcha": img_str, "key": id_key})

验证码通过 django-simple-captcha 生成,存储在数据库中。

第 2 步:用户登录

携带账号、密码、验证码和 Key 发起登录请求。

请求

POST /login/login
Content-Type: application/json

{
    "username": "admin",
    "password": "123456",
    "code": "a3Kp",
    "key": 1
}

成功响应

json
{
    "code": 0,
    "msg": "登录成功",
    "data": {
        "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
        "must_change_password": false
    },
    "ok": true
}
  • access_token:JWT 令牌,后续请求需携带
  • must_change_password:是否需要修改密码

登录流程详解

1. 从请求体获取 username、password、code、key
2. 验证码校验(比对数据库中的验证码记录)
   - 不正确 -> 返回失败
3. 查询用户(username + status=1)
   - 不存在 -> 返回失败
4. 比对密码(bcrypt 双重加盐验证)
   - 不匹配 -> 返回失败
5. 登录成功
   - 生成 JWT Token
   - 记录登录日志
   - 返回 Token

第 3 步:携带 Token 请求业务接口

登录成功后,前端将 Token 存入 localStorage,后续所有请求自动携带。

请求头格式

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...

Token 解析流程

请求进入 -> check_login 装饰器
  +-- 提取 Authorization 头(request.META['HTTP_AUTHORIZATION'])
  +-- 移除 "Bearer " 前缀
  +-- parse_payload() 解析 JWT Token
  |   +-- 验证签名(HS256 + JWT_SALT)
  |   +-- 验证过期时间
  |   +-- 检查黑名单(Redis)
  +-- 验证通过 -> 执行原视图函数
  +-- 验证失败 -> R.failed(code=401, msg=...)

check_login 装饰器实现

python
# middleware/login_middleware.py
def check_login(func):
    def wrapper(request, *args, **kwargs):
        ignoreURL = ['/login', '/captcha']

        if request.path not in ignoreURL:
            access_token = request.headers['Authorization']
            access_token = access_token.replace('Bearer ', "")
            result = parse_payload(access_token)

            if result['code'] != 0:
                return R.failed(code=401, msg=result['msg'])

        return func(request, *args, **kwargs)
    return wrapper

第 4 步:权限校验

每个业务接口通过 PermissionRequired 混入类校验权限。

权限校验流程

请求到达 -> PermissionRequired.has_permission()
  +-- 用户 ID = 1(admin)-> 跳过权限校验,直接放行
  +-- 其他用户
      +-- 查询用户权限列表(menu.services.get_user_permissions)
      +-- 匹配权限字符串
      |   +-- 匹配 -> 放行
      |   +-- 不匹配 -> 返回 "暂无操作权限"

权限字符串格式

sys:{module}:{action}

示例:sys:user:addsys:role:pagesys:article:delete

第 5 步:Token 刷新

Token 支持无感刷新,刷新时生成新令牌。

刷新请求

POST /login/refreshToken
Authorization: Bearer <old_token>

刷新流程

1. 验证原令牌有效性
2. 移除 JWT 标准字段(exp/iat/nbf),保留业务数据
3. 生成新令牌
4. 返回新令牌

第 6 步:退出登录

退出时将当前 Token 加入 Redis 黑名单,使其立即失效。

请求

GET /login/logout
Authorization: Bearer <token>

响应

json
{
    "code": 0,
    "msg": "注销成功",
    "data": null,
    "ok": true
}

退出流程

1. 提取当前 Token
2. 计算 Token 的 SHA256 指纹
3. 存入 Redis 黑名单(TTL = Token 剩余有效期)
4. 记录退出日志
5. 返回成功

Token 黑名单机制

  • 存储方式:Redis,Key 格式 token:blacklist:{sha256_fingerprint}
  • 过期策略:TTL 与 Token 剩余有效期一致,自动清理
  • 故障降级:Redis 不可用时 fail-open 放行(避免全站不可用)

配置项

环境变量默认值说明
JWT_SALT内置默认密钥JWT 签名密钥,建议至少 32 字节
DEFAULT_TIMEOUT_MINUTES20Token 过期时间(分钟)

总结

认证流程覆盖了从获取验证码到退出登录的完整链路,核心安全机制包括:

  • bcrypt 密码哈希 + 随机盐
  • JWT HS256 签名 + 过期校验
  • django-simple-captcha 验证码防暴力破解
  • Token 黑名单即时失效
  • Redis 不可用时 fail-open 降级

小蚂蚁云团队 · 提供技术支持