# 权限与访问控制设计(Nuxt) ## 背景与目标 当前项目已有基础账号体系与会话能力(登录、注册、获取当前用户),但缺少统一的页面与 API 访问控制策略。 本设计目标: - 采用**登录优先**:默认受保护,少量白名单放行 - 保持实现简洁、易维护、可扩展 - 避免常见安全问题(越权、开放重定向、误放行、重定向循环) - 支持“同一路由按登录态展示不同内容”的页面模式 ## 需求结论(已确认) - 白名单配置方式:**代码静态配置** - 页面策略:**默认拦截,白名单放行** - 未登录白名单页面:`/`、`/login`、`/register`,并预留认证辅助页扩展 - 页面展示模式:**混合模式** - 入口页(如 `/`)同路由按登录态切换 - 复杂业务页拆分为受保护路由 - 未登录访问受保护页面:跳转 `/login?redirect=...`,登录后回跳 - API 策略:**默认要求登录**,仅少数白名单 API 放行 ## 总体方案(方案 1) 采用“双层守卫”: 1. 前端全局路由中间件(页面守卫) 2. 服务端 API 中间件(接口守卫) 两层共同保证:即使前端绕过,后端仍可阻断未授权调用。 ## 架构设计 ### 1) 前端会话状态层 新增统一会话组合式函数(例如 `app/composables/useAuthSession.ts`): - 负责请求 `/api/auth/me` - 暴露 `loggedIn`、`user`、`pending`、`refresh`、`clear` - 登录成功后调用 `refresh` - 登出或 `401` 时调用 `clear` 设计原则: - 不在多个页面重复写“是否登录”逻辑 - 统一处理会话失效状态 ### 2) 页面守卫层(全局) 新增 `app/middleware/auth.global.ts`,策略如下: - 默认:页面需要登录 - 放行白名单:`/`、`/login`、`/register` 及后续认证辅助页(代码增补) - 未登录访问受保护页: - 跳转 `/login?redirect=<当前完整路径>` - 已登录访问游客页(如登录/注册页): - 优先跳 `redirect` - 无合法 `redirect` 则跳默认登录后落地页(初始可设为 `/`) ### 3) API 守卫层(服务端) 新增 `server/middleware/10.auth-guard.ts`,策略如下: - 仅处理 `/api/**` - 默认要求登录 - API 白名单(初始): - `/api/auth/login` - `/api/auth/register` - 必要公开读取接口(如 `/api/config/global` 的 GET) - 未登录或无效会话:统一返回 `401` 补充: - 登录态来源统一走 `event.context.auth.getCurrent()`(沿用现有上下文能力) - 不在错误响应中暴露内部实现细节 ## 关键安全设计 ### 1) redirect 安全校验(防开放重定向) `redirect` 仅接受站内相对路径,校验规则: - 必须以 `/` 开头 - 禁止 `//` 开头 - 禁止包含协议(如 `http:`、`https:`、`javascript:`) - 非法值全部降级为默认落地页 ### 2) 白名单匹配规则(防误放行) 采用“精确匹配 + 明确前缀匹配”: - 精确:`/login` - 前缀:`/auth/forgot-password`(示例) 禁止宽泛正则(如 `^/a` 这类容易误伤或放大权限边界的规则)。 ### 3) 未授权语义统一 - API 统一返回 `401` - 页面统一重定向到登录页 - 前端收到 `401` 后同步清理本地会话状态,避免脏态 ### 4) 防重定向循环 - 登录/注册页不再跳自身 - 当 `redirect` 指向游客页本身时,降级到默认落地页 ## 代码组织建议 建议新增/调整文件: - `app/composables/useAuthSession.ts`:会话读取与缓存 - `app/middleware/auth.global.ts`:页面访问控制 - `app/utils/auth-routes.ts`:页面白名单、游客页集合、`redirect` 校验工具 - `server/middleware/10.auth-guard.ts`:API 访问控制 - `server/utils/auth-api-routes.ts`:API 白名单规则 约束: - 路由规则只在一处定义,避免多处分叉 - 页面与 API 规则分别维护,但命名和模式保持一致 ## 数据流与行为流 ### 场景 A:未登录访问受保护页面 1. 用户进入受保护路由 2. `auth.global` 判定未登录 3. 跳转 `/login?redirect=<原路由>` 4. 登录成功后读取并校验 `redirect` 5. 合法则回跳,不合法则去默认页 ### 场景 B:会话过期后访问受保护 API 1. 请求命中 API 守卫 2. 会话失效,返回 `401` 3. 前端拦截到 `401`,清理登录态 4. 若当前在受保护页面,触发跳转到登录页 ### 场景 C:首页双态展示 1. 首页属于白名单可访问 2. 页面内部根据 `loggedIn` 渲染访客态或用户态 3. 刷新会话后自动切换显示 ## 测试与验收标准 ### 路由访问测试 - 未登录访问受保护页应跳登录并保留 `redirect` - 已登录访问登录/注册页应跳转到有效 `redirect` 或默认页 - 白名单页面在未登录下可直接访问 ### API 鉴权测试 - 未登录访问受保护 API 返回 `401` - 白名单 API 未登录可正常请求 - 过期会话访问受保护 API 返回 `401` ### 安全测试 - `redirect` 注入(`http://...`、`//...`、`javascript:...`)均被拦截 - 白名单边界:`/login-xxx` 不应命中 `/login` - 不出现登录页/注册页重定向死循环 ### 体验测试 - 首页登录态切换稳定,不出现明显错误闪烁 - 登出后状态同步清空,页面表现与未登录一致 ## 渐进实施顺序 1. 增加路由规则常量与 `redirect` 校验工具 2. 落地前端全局中间件(先页面策略) 3. 落地 API 守卫中间件(再后端兜底) 4. 接入统一会话组合式函数,替换分散状态判断 5. 补齐回归测试与手工验收清单 ## 非目标(本次不做) - 角色/权限点(RBAC)细粒度控制 - 多租户权限隔离 - OAuth、SSO 等第三方登录 - 动态远端下发白名单配置 ## 风险与应对 - 风险:误将必要接口放入受保护导致前端初始化失败 应对:白名单初始集合最小化后,逐个验证关键页面 - 风险:新增页面忘记考虑权限属性 应对:默认拦截策略天然兜底,新增页面仅在确需公开时加入白名单 - 风险:重定向逻辑复杂导致跳转异常 应对:`redirect` 工具函数集中实现,并覆盖异常值测试 ## 设计结论 在当前项目阶段,采用“前端全局路由守卫 + 服务端 API 统一守卫”的登录优先模型,能以最小复杂度建立稳定安全边界,并保留后续扩展(认证辅助页、业务页拆分、角色权限)的空间。