Day 08 · 用户认证与权限:谁能干什么

发布时间:2026/10/16 6:18:14
Day 08 · 用户认证与权限:谁能干什么
摘要接口写好了但问题来了——谁都能调商品列表可以公开但购物车总不能谁都能看吧后台管理更不能让普通用户乱进。今天解决谁能干什么这个问题JWT 认证体系注册、登录、Token 刷新、接口鉴权一条龙再配上 DRF 权限类——公开接口、登录接口、管理员接口三层清清楚楚最后做一轮安全加固。成本0。接口写好了但不是谁都能调用上一篇我们把商品 API 写出来了/api/goods/一访问数据哗哗就出来了。但你有没有想过——这些数据谁都能看谁都能改想想商城的业务场景商品列表所有人都能看没登录也能浏览购物车必须登录不然购物车加到谁头上了下单必须登录得知道是谁下的单后台管理只有管理员能进普通用户连门都别想如果你不做任何认证和权限控制接口就是裸奔状态——任何人都可以往别人的购物车里塞东西用别人的账号下单直接调删除接口把商品全删了这不是危言耸听这是真实会被攻击的漏洞。所以今天要做的事很明确给接口装上门禁系统。谁能进哪个门刷什么卡全在今天搞清楚。先分清两件事认证 vs 授权很多人把认证和授权搞混其实是两码事认证Authentication你是谁→ 登录验证证明你是你授权Authorization你能干什么→ 权限判断决定你能访问什么打个比方认证是进小区刷脸确认你是业主授权是确认你能进哪栋楼。DRF 里对应两个概念authentication_classes决定怎么认证用户我们用 JWTpermission_classes决定用户能干什么权限类为什么不用 Session用 JWT传统 Web 用 Session Cookie 做认证用户登录后服务器生成一个 Session ID 存在 Cookie 里每次请求带上。问题是前后端分离架构下后端是纯 API前端可能是 Vue、React、小程序、APPCookie 方案在跨域场景下非常麻烦。JWTJSON Web Token是目前前后端分离架构最主流的认证方案。原理用户登录用户名 密码 ↓ 服务器验证通过后生成一个 Token一段加密的 JSON 字符串 ↓ Token 返回给前端前端存起来 ↓ 之后每次请求在 Header 里带上 Token ↓ 服务器收到请求解析 Token就知道是谁了JWT 的好处无状态服务器不用存 SessionToken 自己携带所有信息跨域友好不依赖 Cookie前后端分离完美适配多端通用Web、APP、小程序一套方案全搞定Token 长这样三段用.隔开编码了用户信息和过期时间。你不用看懂它——djangorestframework-simplejwt会自动生成和验证eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE3MDAwMDAwMDB9.xxxxxDRF 内置权限类一览权限类谁能访问典型场景AllowAny所有人包括未登录商品列表、商品详情、注册IsAuthenticated已登录用户购物车、下单、个人中心IsAdminUser管理员is_staffTrue后台管理、商品管理IsAuthenticatedOrReadOnly登录可改未登录只能看评论、收藏不够用自己写一个就行。这就是 DRF 权限体系优雅的地方——继承一个类重写一个方法自定义权限就出来了。动手从零搭认证与权限体系装依赖pipinstalldjangorestframework-simplejwtdjangorestframework-simplejwt是 DRF 官方推荐的 JWT 插件跟 DRF 配合得天衣无缝。配置 JWTsettings.py里加上框架配置照抄改参数# settings.pyfromdatetimeimporttimedelta REST_FRAMEWORK{# 默认认证方式JWTDEFAULT_AUTHENTICATION_CLASSES:[rest_framework_simplejwt.authentication.JWTAuthentication,],# 默认权限必须登录全局兜底具体接口可覆盖DEFAULT_PERMISSION_CLASSES:[rest_framework.permissions.IsAuthenticated,],DEFAULT_PAGINATION_CLASS:rest_framework.pagination.PageNumberPagination,PAGE_SIZE:20,# 限流防脚本疯狂刷接口DEFAULT_THROTTLE_CLASSES:[rest_framework.throttling.AnonRateThrottle,rest_framework.throttling.UserRateThrottle,],DEFAULT_THROTTLE_RATES:{anon:30/minute,# 未登录每分钟最多 30 次user:100/minute,# 已登录每分钟最多 100 次},}SIMPLE_JWT{ACCESS_TOKEN_LIFETIME:timedelta(hours2),# Access 有效期 2h短一点丢了影响小REFRESH_TOKEN_LIFETIME:timedelta(days7),# Refresh 有效期 7 天用户体验好ROTATE_REFRESH_TOKENS:True,# 每次刷新轮换新 TokenBLACKLIST_AFTER_ROTATION:True,# 轮换后旧 Token 失效黑名单}配置说明逻辑不是背代码ACCESS_TOKEN_LIFETIMEAccess Token 有效期前端每次请求带在 Header 里那个短一点安全丢了最多影响 2 小时。REFRESH_TOKEN_LIFETIMERefresh Token 有效期用来换新的 Access Token不用每次都重新登录。ROTATE_REFRESH_TOKENSBLACKLIST_AFTER_ROTATION每次刷新都发新 Refresh Token、旧的作废——即使旧 Token 被泄露用过一次就失效。这是安全最佳实践。限流未登录每分钟 30 次已登录每分钟 100 次。注册和登录接口用户注册是商城用户系统的核心入口关键逻辑就三块核心片段# mall/views.pyfromrest_framework_simplejwt.tokensimportRefreshToken Userget_user_model()# 注册接口不需要登录api_view([POST])permission_classes([AllowAny])defregister(request):usernamerequest.data.get(username)passwordrequest.data.get(password)emailrequest.data.get(email,)# ① 基础校验用户名密码不能为空ifnotusernameornotpassword:returnResponse({code:400,message:用户名和密码不能为空},statusstatus.HTTP_400_BAD_REQUEST)# ② 查重用户名不能已存在ifUser.objects.filter(usernameusername).exists():returnResponse({code:400,message:用户名已存在},statusstatus.HTTP_400_BAD_REQUEST)# ③ 创建用户密码自动加密存储这是 Django 的功劳userUser.objects.create_user(usernameusername,passwordpassword,emailemail)# 注册成功自动登录生成 Token 返回refreshRefreshToken.for_user(user)returnResponse({code:0,message:注册成功,data:{user_id:user.id,username:user.username,access:str(refresh.access_token),refresh:str(refresh)},},statusstatus.HTTP_201_CREATED)# 获取当前用户信息必须登录api_view([GET])permission_classes([IsAuthenticated])defuser_profile(request):userrequest.user# DRF 认证后request.user 就是当前用户returnResponse({code:0,message:success,data:{user_id:user.id,username:user.username,email:user.email,date_joined:user.date_joined}})JWT 认证完整流程1. 注册 → POST /api/auth/register/ → 返回 access refresh Token 2. 登录 → POST /api/token/ → 返回 access refreshsimplejwt 自带 3. 刷新 → POST /api/token/refresh/ → 用 refresh 换新的 accesssimplejwt 自带 4. 调接口 → 请求头带 Authorization: Bearer access_token登录和刷新接口 simplejwt 已经写好注册路由即可# mall/urls.pyfromrest_framework_simplejwt.viewsimportTokenObtainPairView,TokenRefreshView urlpatterns[path(auth/register/,views.register,nameregister),path(auth/profile/,views.user_profile,nameprofile),# JWT 登录 刷新simplejwt 自带path(token/,TokenObtainPairView.as_view(),nametoken_obtain_pair),path(token/refresh/,TokenRefreshView.as_view(),nametoken_refresh),]测试# 1. 登录拿 Tokencurl-XPOST http://127.0.0.1:8000/api/token/\-HContent-Type: application/json\-d{username: testuser, password: testpass123}# 返回{ access: eyJ..., refresh: eyJ... }# 2. 带 Token 访问接口curlhttp://127.0.0.1:8000/api/auth/profile/-HAuthorization: Bearer eyJ...# 返回{ code: 0, message: success, data: { user_id: 1, ... } }# 3. 不带 Token 访问 → 401curlhttp://127.0.0.1:8000/api/auth/profile/自定义权限类VIP 专属接口内置权限类够用但业务需要更细粒度时自己写一个通用模式可复用# mall/permissions.pyfromrest_framework.permissionsimportBasePermissionclassIsVIPUser(BasePermission):只有 VIP 用户能访问message该功能仅对 VIP 用户开放defhas_permission(self,request,view):ifnotrequest.userornotrequest.user.is_authenticated:returnFalsereturngetattr(request.user,is_vip,False)classIsOwnerOrAdmin(BasePermission):只有数据所有者或管理员能操作典型用户只能编辑自己的订单message你只能操作自己的数据defhas_object_permission(self,request,view,obj):ifrequest.user.is_staff:returnTruereturnobj.userrequest.user权限类的两个方法has_permission(request, view)接口级检查——“这类用户能不能访问这个接口”has_object_permission(request, view, obj)对象级检查——“这个用户能不能操作这条数据”比如普通用户能访问订单列表接口has_permission通过但只能看自己的订单has_object_permission过滤。给接口分配权限回到商城实际业务核心思路是按方法/角色分层核心片段classGoodsViewSet(viewsets.ModelViewSet):querysetGoods.objects.filter(status1)# 只展示上架商品defget_permissions(self):根据请求方法动态分配权限ifself.actionin[list,retrieve]:return[AllowAny()]# 浏览商品所有人return[IsAdminUser()]# 增删改仅管理员classOrderViewSet(viewsets.ModelViewSet):permission_classes[IsAuthenticated]# 基础门槛必须登录defget_queryset(self):# 普通用户只看自己的订单管理员看全部userself.request.userifuser.is_staff:returnOrder.objects.all()returnOrder.objects.filter(useruser)defperform_create(self,serializer):serializer.save(userself.request.user)# 创建时自动绑定当前用户权限分配总览接口权限类谁能访问商品列表GET /api/goods/AllowAny所有人商品详情GET /api/goods/{id}/AllowAny所有人商品增删改POST/PUT/DELETEIsAdminUser仅管理员订单列表/详情IsAuthenticated登录用户只看自己的创建订单IsAuthenticated登录用户用户注册AllowAny所有人个人信息IsAuthenticated登录用户路由用DefaultRouter注册goods/orders/admin/goods分别对应不同权限的 ViewSet认证路由跟上面对齐即可。安全加固上生产前的必修课接口权限搞定了但安全不能只做到这一步。密码策略弱密码是安全事故的头号元凶。“123456”、“password” 这些攻击者甚至不需要工具就能猜到。Django 的密码验证器在注册时就拦截配置可直接用# settings.pyAUTH_PASSWORD_VALIDATORS[{NAME:django.contrib.auth.password_validation.UserAttributeSimilarityValidator},{NAME:django.contrib.auth.password_validation.MinimumLengthValidator,OPTIONS:{min_length:8}},{NAME:django.contrib.auth.password_validation.CommonPasswordValidator},{NAME:django.contrib.auth.password_validation.NumericPasswordValidator},]分别是不能和用户名太相似、最少 8 位、不能太常见内置 2 万 常见密码黑名单、不能全是数字。CORS 跨域配置前后端分离前端和后端通常不在同一域名。浏览器的同源策略会阻止跨域请求需要后端配置 CORS 放行。pipinstalldjango-cors-headers# settings.pyINSTALLED_APPS[...,corsheaders]MIDDLEWARE[corsheaders.middleware.CorsMiddleware,# 放在最前面...]CORS_ALLOW_ALL_ORIGINSFalse# 生产环境永远 FalseCORS_ALLOWED_ORIGINS[# 只允许特定域名https://www.meiyada.com,https://mall.meiyada.com,]CORS_ALLOW_CREDENTIALSTrue注意CORS_ALLOW_ALL_ORIGINS True只适合开发调试。生产环境一定要写明白域名列表不然任何人都可以从自己的网站调你的接口——等于把大门敞开。Rate Limiting 限流前面REST_FRAMEWORK里已加了基础限流。注册这种敏感接口还可以更严——每个 IP 每小时只能注册几次核心思路classRegisterThrottle(AnonRateThrottle):rate3/hour# 每个 IP 每小时最多 3 次注册api_view([POST])permission_classes([AllowAny])defregister(request):throttleRegisterThrottle()ifnotthrottle.allow_request(request,register):returnResponse({code:429,message:注册过于频繁请稍后再试},status429)# ... 注册逻辑防止有人写脚本批量注册机器人账号。Django 安全中间件Django 自带安全中间件生产环境务必开启# settings.pySECURE_BROWSER_XSS_FILTERTrue# 浏览器 XSS 过滤SECURE_CONTENT_TYPE_NOSNIFFTrue# 禁止猜测 Content-TypeSESSION_COOKIE_SECURETrue# Cookie 只在 HTTPS 下传输CSRF_COOKIE_SECURETrue# CSRF Token 只在 HTTPS 下传输SECURE_SSL_REDIRECTTrue# 强制 HTTP 跳转 HTTPSSECURE_HSTS_SECONDS31536000# HSTS一年内强制 HTTPSSECURE_HSTS_INCLUDE_SUBDOMAINSTrue# HSTS 覆盖所有子域名这些从多个维度防攻击XSS 过滤防跨站脚本、Content-Type 防 MIME 嗅探、Cookie 防明文窃取、HSTS 防降级攻击。一个容易踩的坑Django 4.0 的CSRF_TRUSTED_ORIGINS必须带 schemehttps://只写域名不生效CSRF_TRUSTED_ORIGINS[https://www.meiyada.com,# ✅ 带 scheme# www.meiyada.com, # ❌ 缺 https://不生效]让 AI 帮你生成权限配置写权限配置和安全检查清单这种重复性工作AI 特别擅长。把提示词发给 Trae 或 Cursor我有一个 Django REST Framework 项目包含以下接口 - 商品列表/详情公开 - 商品增删改管理员 - 订单列表/创建/详情登录用户 - 购物车增删改查登录用户只能操作自己的 - 用户注册/登录/个人信息 - 后台管理管理员 请帮我 1. 为每个接口设计权限类AllowAny / IsAuthenticated / IsAdminUser / 自定义 2. 写一个 IsOwnerOrAdmin 自定义权限类 3. 生成完整的权限配置代码 4. 列出一份安全检查清单密码策略、CORS、CSRF、Rate Limiting、HTTPS 等 5. 给出 settings.py 中的安全相关配置AI 返回代码后重点审这几点权限类是否匹配业务逻辑比如商品列表应该是AllowAny如果 AI 写成IsAuthenticated没登录就看不到商品了——显然不对。get_queryset是否正确隔离数据普通用户只能看自己的管理员看全部。限流参数是否合理太松等于没限太严影响正常用户体验。学完来练练手改造题 1给购物车加自定义权限用户只能查看和操作自己的购物车创建IsCartOwner权限类 → 应用到购物车视图 → 确保get_queryset只返回当前用户的数据。改造题 2实现 Token 黑名单用户注销后让 Token 立即失效在INSTALLED_APPS加rest_framework_simplejwt.token_blacklist确认BLACKLIST_AFTER_ROTATION已开启写一个登出接口POST /api/auth/logout/把 Refresh Token 加黑名单测试注销后用旧 Token 访问返回 401。JWT 官方文档djangorestframework-simplejwt。安全配置参考 Django 官方 security 文档。本系列为梅雅达编程笔记原创首发于 CSDN。有问题评论区见