/** * CSRF Token 管理 * * CSRF 保护机制: * 1. GET 请求获取 CSRF Token(从 cookie 或 API) * 2. POST/PUT/DELETE 请求将 Token 添加到 X-CSRF-Token 头 * * 注意:由于使用 Bearer Token 认证(存储在内存中), * CSRF 风险相对较低,但为增强安全性仍建议对关键操作启用。 */ // 注意:避免从 './client' 导入,防止循环依赖 // 使用原生 fetch 获取 CSRF Token import { config } from '@/lib/config' import { getAccessToken } from './auth-session' // CSRF Token 存储 let csrfToken: string | null = null /** * 获取 CSRF Token */ export function getCSRFToken(): string | null { return csrfToken } /** * 设置 CSRF Token */ export function setCSRFToken(token: string): void { csrfToken = token } /** * 从 cookie 中读取 CSRF Token * Django/Laravel 等框架通常在 cookie 中设置 csrftoken */ export function getCSRFTokenFromCookie(): string | null { if (typeof document === 'undefined') { return null } const match = document.cookie.match(/csrftoken=([^;]+)/) return match ? match[1] : null } /** * 解析 API 基础 URL * 注意:此函数复制自 client.ts 以避免循环依赖 */ function resolveApiBaseUrl(): URL { const origin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost' const rawBaseUrl = /^https?:\/\//i.test(config.apiBaseUrl) ? config.apiBaseUrl : config.apiBaseUrl.startsWith('/') ? config.apiBaseUrl : `/${config.apiBaseUrl}` const baseUrl = new URL(rawBaseUrl, origin) if (!baseUrl.pathname.endsWith('/')) { baseUrl.pathname = `${baseUrl.pathname}/` } return baseUrl } /** * 构建完整 URL */ function buildUrl(path: string): string { const normalizedPath = path.replace(/^\/+/, '') const url = new URL(normalizedPath, resolveApiBaseUrl()) return url.toString() } /** * 初始化 CSRF Token * 从 cookie 或 API 获取 Token 并存储 */ export async function initCSRFToken(): Promise { // 优先从 cookie 获取 let token = getCSRFTokenFromCookie() if (!token) { try { const headers: Record = { 'Content-Type': 'application/json', } const accessToken = getAccessToken() if (accessToken) { headers.Authorization = `Bearer ${accessToken}` } // 使用原生 fetch 避免循环依赖 const response = await fetch(buildUrl('/auth/csrf-token'), { method: 'GET', credentials: 'include', headers, }) if (response.ok) { const result = await response.json() // 后端返回字段名为 csrf_token if (result.code === 0 && result.data?.csrf_token) { token = result.data.csrf_token } } } catch { // API 不支持,使用 cookie 中的 token(如果有) token = getCSRFTokenFromCookie() } } if (token) { setCSRFToken(token) } return token } /** * 清除 CSRF Token(登出时调用) */ export function clearCSRFToken(): void { csrfToken = null } /** * CSRF Token 头名称 */ export const CSRF_HEADER_NAME = 'X-CSRF-Token' /** * 获取带 CSRF Token 的请求头 * 用于 POST/PUT/DELETE 请求 */ export function getCSRFHeaders(): Record { const token = csrfToken || getCSRFTokenFromCookie() if (!token) { return {} } return { [CSRF_HEADER_NAME]: token } } /** * 需要 CSRF 保护的方法列表 */ export const CSRF_PROTECTED_METHODS = ['POST', 'PUT', 'DELETE', 'PATCH']