Files
2026-07-13 21:35:57 +08:00

173 lines
4.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: auth-flow-patterns
description: 完整的认证流程模式,涵盖前端与后端
metadata:
type: reference
---
# 认证流程模式
前端和后端的完整认证流程。涵盖 JWT Bearer 流程、自动令牌刷新、Next.js 服务端认证、RBAC 以及后端中间件顺序。
---
## JWT Bearer 流程(最常见)
```
1. 登录
客户端 → POST /api/auth/login { email, password }
服务端 → { accessToken15分钟), refreshToken7天,httpOnly cookie }
2. 认证请求
客户端 → GET /api/orders Authorization: Bearer <accessToken>
服务端 → 验证 JWT → 返回数据
3. 令牌刷新(无感透明)
客户端 → 收到 401 → POST /api/auth/refreshcookie 自动发送)
服务端 → 新的 accessToken
客户端 → 使用新令牌重试原始请求
4. 登出
客户端 → POST /api/auth/logout
服务端 → 使 refresh token 失效 → 清除 cookie
```
---
## 前端:自动令牌刷新
```typescript
// lib/api-client.ts — 添加到现有的 fetch 封装中
async function apiWithRefresh<T>(path: string, options: RequestInit = {}): Promise<T> {
try {
return await api<T>(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<T>(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 <DashboardContent user={user} />;
}
```
---
## 后端:标准中间件顺序
```
请求 → 1.请求ID → 2.日志 → 3.CORS → 4.限流 → 5.请求体解析
→ 6.认证 → 7.授权 → 8.校验 → 9.处理器 → 10.错误处理 → 响应
```
---
## 后端:JWT 规则
```
✅ 短时效的 access token15分钟)+ 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 tokenhttpOnly cookieXSS 安全)
✅ 收到 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(不能使用 `*`