Django 学习日记(补充1)| 彻底吃透:自定义 JWT 认证 + 全局登录中间件
大家好这是我 Django 学习日记的第三篇。上一篇我们把路由、反向解析、DRF 自动路由、媒体文件、跨域全部讲明白了。今天我们进入整个项目最核心、最安全、最关键的部分用户登录认证体系在进入视图前的一篇补充文章。本文将从JWT 是什么→手写 JWT 生成→自定义 DRF 认证类→自定义全局登录中间件→白名单、匿名用户、异常处理全部逐行拆解。你开发中遇到的登录、token、中间件问题这一篇全部解决。一、本篇你能学到什么JWT 登录令牌如何生成自定义 DRF JWT 认证类逐行讲解自定义 Django 全局登录中间件企业级实战整个登录认证完整流程二、什么是 JWT为什么要用它一句话总结JWTJSON Web Token就是前端的 “登录身份证”。前后端分离项目中服务器不保存 session所以必须给前端发一个令牌登录成功 → 发放 JWT后续请求 → 携带 JWT服务器验证 JWT → 确认身份JWT 长这样plaintextaaaaa.bbbbb.ccccc分为三部分头部(声明加密算法)、载荷(存放自定义信息)、签名(对前两部分加密防止篡改)。三、第一步手写 JWT 生成函数我们先手写一个工具函数登录成功后生成 token。代码位置apps/oaauth/authentications.pypython运行import jwt import time from django.conf import settings # 生成 JWT Token def generate_jwt(user): # 过期时间7天 expire_time time.time() 60 * 60 * 24 * 7 # 传入用户ID 过期时间 项目密钥(传入自定义内容即载荷) return jwt.encode( {userid: user.pk, exp: expire_time}, keysettings.SECRET_KEY )关键讲解返回格式是标准 JWT不是自定义userid 是我们自己存在 token 里的exp 是 JWT 标准过期字段必须使用 SECRET_KEY 密钥签名四、第二步自定义 DRF JWT 认证类这是 DRF 官方规范的认证类专门验证 JWT 是否合法。全代码import jwt import time from django.conf import settings from rest_framework.authentication import BaseAuthentication, get_authorization_header, TokenAuthentication from rest_framework import exceptions from jwt.exceptions import ExpiredSignatureError from .models import OAUser#这里根据自己的项目来定义我的用户模型定义为OAUser def generate_jwt(user): expire_time time.time() 60 * 60 * 24 * 7 return jwt.encode({userid: user.pk, exp: expire_time}, keysettings.SECRET_KEY) class UserTokenAuthentication(BaseAuthentication): def authenticate(self, request): # 这里的request是rest_framework.request.Request对象 # 这里最后返回的是Django的原生request请求而不是DRF封装过的request请求 return request._request.user, request._request.auth class JWTAuthentication(BaseAuthentication): #定义关键字 keyword JWT def authenticate(self, request): #JWT令牌内容为JWT空格生成的token auth get_authorization_header(request).split() #如果令牌格式不合法或者不存在JWT令牌不做JWT验证直接放行由后续流程统一处理 if not auth or auth[0].lower() ! self.keyword.lower().encode(): return None if len(auth) 1: msg 不可用的JWT请求头 raise exceptions.AuthenticationFailed(msg) elif len(auth) 2: msg 不可用的JWT请求头JWT Token中间不应该有空格 raise exceptions.AuthenticationFailed(msg) try: jwt_token auth[1] jwt_info jwt.decode(jwt_token, settings.SECRET_KEY, algorithmsHS256) userid jwt_info.get(userid) try: # 绑定当前user到request对象上 user OAUser.objects.get(pkuserid) setattr(request,user, user) return user, jwt_token except: msg 用户不存在 raise exceptions.AuthenticationFailed(msg) except ExpiredSignatureError: msg JWT Token已过期 raise exceptions.AuthenticationFailed(msg)可能的疑问1. auth get_authorization_header(request).split()获取请求头并分割成[JWT, token字符串]2. if not auth or ... return None不是报错是跳过交给下一个认证类处理3. 为什么 len (auth) 必须等于 2因为格式必须是JWT xxxxxxx4. jwt.decode 三个参数干什么jwt_token要解码的 tokenSECRET_KEY必须用项目密钥验证防篡改HS256加密算法5. return user, jwt_tokenPython 支持返回多个值 → 本质返回元组DRF 认证类必须这样返回五、第三步桥接中间件与 DRF 的认证类class UserTokenAuthentication(BaseAuthentication): def authenticate(self, request): # 直接拿中间件已经验证好的用户 return request._request.user, request._request.auth UserTokenAuthentication 这个类的作用 将 DRF 包装的 request 对象 降级获取到 Django 原生 request 对象 从中取出中间件已经验证好的 user 和 auth 返回给 DRF 框架使用。 核心讲解在 DRF 框架中视图收到的 request 并不是 Django 原生 request而是 DRF 经过包装、增强后的rest_framework.request.Request对象。但我们的登录中间件是 Django 层面的中间件它把登录用户绑定到了原生 Django request 对象上。因此我们需要一个 “桥接” 的认证类这个类不做任何验证只负责 “桥接”从原生 request 中取出用户 → 交给 DRF 认证体系。六、最核心自定义 Django 全局登录中间件这是本篇最重点、最实战、企业级必用的部分。代码位置apps/oaauth/middlewares.py完整代码from django.utils.deprecation import MiddlewareMixin from rest_framework.authentication import get_authorization_header from rest_framework import exceptions import jwt from django.conf import settings from django.contrib.auth import get_user_model from django.http.response import JsonResponse from rest_framework.status import HTTP_403_FORBIDDEN from jwt.exceptions import ExpiredSignatureError from django.contrib.auth.models import AnonymousUser OAUser get_user_model() class LoginCheckMiddleware(MiddlewareMixin): keyword JWT def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 白名单不需要登录就能访问 self.white_list [/auth/login, /staff/active] #先判断是否有令牌有则进行令牌校验否则直接抛出异常(因为没令牌说明没登录) def process_view(self, request, view_func, view_args, view_kwargs): # 白名单判断 if request.path in self.white_list: request.user AnonymousUser() request.auth None return None # 开始校验 JWTtry 里面全部是登录校验 try: auth get_authorization_header(request).split() if not auth or auth[0].lower() ! self.keyword.lower().encode(): raise exceptions.ValidationError(请传入JWT) # 格式判断 if len(auth) 1: raise exceptions.AuthenticationFailed(无效的JWT请求头) elif len(auth) 2: raise exceptions.AuthenticationFailed(JWT不能包含空格) # 解码 jwt_token auth[1] jwt_info jwt.decode(jwt_token, settings.SECRET_KEY, algorithms[HS256]) userid jwt_info.get(userid) user OAUser.objects.get(pkuserid) # 绑定用户 request.user user request.auth jwt_token # 验证失败 → 未登录 except Exception as e: return JsonResponse({detail: 请先登录}, statusHTTP_403_FORBIDDEN)七、中间件逐行拆解1. 为什么中间件要放在 oaauth 用户模块因为登录认证属于用户模块职责中间件强依赖用户模型企业级规范谁管理登录中间件就放谁那2. 为什么必须用 get_user_model ()因为中间件加载时模型还未初始化直接导入会报错3.init方法干什么初始化白名单登录页面、激活页面必须放行否则永远无法登录。白名单在 __init__ 中只初始化一次提高效率。4. super().init干什么调用父类构造方法必须写不写中间件报错5. 白名单代码到底干什么if request.path in self.white_list: request.user AnonymousUser() request.auth None return None讲解白名单接口不验证登录必须设置匿名用户保证视图 request.user 一定存在return None →放行继续执行视图6. 为什么要匿名用户Django 所有视图默认request.user一定存在不设置会直接崩溃。7. try 里面的代码干什么对非白名单接口进行 JWT 登录校验8. try 失败为什么代表未登录因为没有 Token / Token 错误 / Token 过期 未登录9. 为什么返回 JsonResponse给前端返回标准 JSON 错误提示八、整个登录认证执行流程前端请求 → 中间件白名单→ 放行非白名单→ JWT 校验校验成功→ 绑定用户校验失败→ 返回请先登录中间件通过 → DRF 认证类直接拿中间件的用户DRF 认可登录状态九、中间件注册必须配置settings.pyMIDDLEWARE [ ... apps.oaauth.middlewares.LoginCheckMiddleware,#放在所有中间件最后 ]十、DRF 认证配置python运行REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ apps.oaauth.authentications.UserTokenAuthentication, ] }十一、本篇总结JWT 就是登录身份证generate_jwt 生成令牌JWTAuthentication 验证令牌中间件做全局登录拦截白名单放行登录接口匿名用户保证视图不崩溃return None 代表放行我的分享到这里就结束了如有错误欢迎指正
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2453806.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!