--- name: auth-flow-patterns description: 完整的认证流程模式,涵盖前端与后端 metadata: type: reference --- # 认证流程模式 前端和后端的完整认证流程。涵盖 JWT Bearer 流程、自动令牌刷新、Next.js 服务端认证、RBAC 以及后端中间件顺序。 --- ## JWT Bearer 流程(最常见) ``` 1. 登录 客户端 → POST /api/auth/login { email, password } 服务端 → { accessToken(15分钟), refreshToken(7天,httpOnly cookie) } 2. 认证请求 客户端 → GET /api/orders Authorization: Bearer 服务端 → 验证 JWT → 返回数据 3. 令牌刷新(无感透明) 客户端 → 收到 401 → POST /api/auth/refresh(cookie 自动发送) 服务端 → 新的 accessToken 客户端 → 使用新令牌重试原始请求 4. 登出 客户端 → POST /api/auth/logout 服务端 → 使 refresh token 失效 → 清除 cookie ``` --- ## 前端:自动令牌刷新 ```typescript // lib/api-client.ts — 添加到现有的 fetch 封装中 async function apiWithRefresh(path: string, options: RequestInit = {}): Promise { try { return await api(path, options); } catch (err) { if (err instanceof ApiError && err.status === 401) { // 尝试刷新 const refreshed = await api<{ accessToken: string }>('/api/auth/refresh', { method: 'POST', credentials: 'include', // 发送 httpOnly cookie }); setAuthToken(refreshed.accessToken); // 重试原始请求 return api(path, options); } throw err; } } ``` --- ## Next.js:服务端认证(App Router) ```typescript // middleware.ts — 在服务端保护路由 import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const token = request.cookies.get('session')?.value; if (!token && request.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.redirect(new URL('/login', request.url)); } return NextResponse.next(); } // app/dashboard/page.tsx — 带认证的服务端组件 import { cookies } from 'next/headers'; export default async function Dashboard() { const token = (await cookies()).get('session')?.value; const user = await fetch(`${process.env.API_URL}/api/me`, { headers: { Authorization: `Bearer ${token}` }, }).then(r => r.json()); return ; } ``` --- ## 后端:标准中间件顺序 ``` 请求 → 1.请求ID → 2.日志 → 3.CORS → 4.限流 → 5.请求体解析 → 6.认证 → 7.授权 → 8.校验 → 9.处理器 → 10.错误处理 → 响应 ``` --- ## 后端:JWT 规则 ``` ✅ 短时效的 access token(15分钟)+ refresh token(服务端存储) ✅ 最小化声明:userId、roles(不包含整个用户对象) ✅ 定期轮换签名密钥 ❌ 切勿将令牌存储在 localStorage 中(存在 XSS 风险) ❌ 切勿在 URL 查询参数中传递令牌(会被记录) ``` --- ## 后端:RBAC 模式 ```typescript function authorize(...roles: Role[]) { return (req, res, next) => { if (!req.user) throw new UnauthorizedError(); if (!roles.some(r => req.user.roles.includes(r))) throw new ForbiddenError(); next(); }; } router.delete('/users/:id', authenticate, authorize('admin'), deleteUser); ``` --- ## 认证方式决策表 | 方法 | 适用场景 | 前端 | |--------|------|----------| | Session | 同域、SSR、Django 模板 | Django templates / htmx | | JWT | 跨域、SPA、移动端 | React, Vue, 移动端应用 | | OAuth2 | 第三方登录、API 消费者 | 任何 | --- ## 铁律 ``` ✅ Access token:短时效(15分钟),存于内存 ✅ Refresh token:httpOnly cookie(XSS 安全) ✅ 收到 401 时自动无感刷新 ✅ 刷新失败时重定向到登录页 ❌ 切勿将令牌存储在 localStorage 中(存在 XSS 风险) ❌ 切勿在 URL 查询参数中传递令牌(会被记录) ❌ 切勿仅依赖客户端认证检查(服务端必须验证) ``` --- ## 常见问题 ### 问题 1:「页面加载时认证正常,导航后失效」 **原因:** 令牌存储在组件状态中(组件卸载后丢失)。 **解决方案:** 将 access token 存储在持久化位置: - React Context(导航后仍存活,页面刷新后丢失) - Cookie(刷新后仍存活) - React Query 缓存,对 session 设置 `staleTime: Infinity` ### 问题 2:「认证请求出现 CORS 错误」 **原因:** 前端缺少 `credentials: 'include'` 或后端 CORS 配置中缺少 `credentials: true`。 **解决方案:** 1. 前端:`fetch(url, { credentials: 'include' })` 2. 后端:`cors({ origin: 'https://your-frontend.com', credentials: true })` 3. 后端:使用凭据时必须指定明确的 origin(不能使用 `*`)