Files
skillhub-052-fullstack-dev/references/auth-flow.md
T
2026-07-13 21:35:57 +08:00

4.8 KiB
Raw Blame History

name, description, metadata
name description metadata
auth-flow-patterns 完整的认证流程模式,涵盖前端与后端
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

前端:自动令牌刷新

// 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

// 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 模式

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(不能使用 *