// Next.js middleware:CSP 安全头 + 认证守卫 // 依据:02-architecture-design.md §13 安全硬化(P6-6)、project_rules §4 安全规范 // - CSP:限制脚本/样式/图片/连接来源 // - 认证守卫:/parent/* 路由检查 token(SSR 阶段重定向) import { NextResponse, type NextRequest } from "next/server"; const PROTECTED_ROUTES = ["/parent"]; export function middleware(request: NextRequest) { const { pathname } = request.nextUrl; const response = NextResponse.next(); // CSP 安全头 const csp = [ "default-src 'self'", "script-src 'self' 'unsafe-inline' 'unsafe-eval'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: https:", "font-src 'self' data:", "connect-src 'self' ws: wss:", "frame-ancestors 'self'", "base-uri 'self'", "form-action 'self'", ].join("; "); response.headers.set("Content-Security-Policy", csp); response.headers.set("X-Frame-Options", "SAMEORIGIN"); response.headers.set("X-Content-Type-Options", "nosniff"); response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin"); response.headers.set( "Permissions-Policy", "camera=(), microphone=(), geolocation=()", ); // 认证守卫(SSR 阶段) if (PROTECTED_ROUTES.some((route) => pathname.startsWith(route))) { const token = request.cookies.get("parent_access_token")?.value; // 注意:F12 裁决使用 localStorage token,SSR 阶段无法读取 // 此处仅做 cookie 兜底检查,客户端 parent/layout.tsx 做完整守卫 if (!token) { // 检查是否有 Authorization header(MF 模式由 Shell 注入) const authHeader = request.headers.get("authorization"); if (!authHeader) { const loginUrl = new URL("/login", request.url); return NextResponse.redirect(loginUrl); } } } return response; } export const config = { matcher: [ "/((?!api|_next/static|_next/image|favicon.ico|mockServiceWorker.js|manifest.json|sw.js|icon-).*)", ], };