You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6.3 KiB
6.3 KiB
权限与访问控制设计(Nuxt)
背景与目标
当前项目已有基础账号体系与会话能力(登录、注册、获取当前用户),但缺少统一的页面与 API 访问控制策略。
本设计目标:
- 采用登录优先:默认受保护,少量白名单放行
- 保持实现简洁、易维护、可扩展
- 避免常见安全问题(越权、开放重定向、误放行、重定向循环)
- 支持“同一路由按登录态展示不同内容”的页面模式
需求结论(已确认)
- 白名单配置方式:代码静态配置
- 页面策略:默认拦截,白名单放行
- 未登录白名单页面:
/、/login、/register,并预留认证辅助页扩展 - 页面展示模式:混合模式
- 入口页(如
/)同路由按登录态切换 - 复杂业务页拆分为受保护路由
- 入口页(如
- 未登录访问受保护页面:跳转
/login?redirect=...,登录后回跳 - API 策略:默认要求登录,仅少数白名单 API 放行
总体方案(方案 1)
采用“双层守卫”:
- 前端全局路由中间件(页面守卫)
- 服务端 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:未登录访问受保护页面
- 用户进入受保护路由
auth.global判定未登录- 跳转
/login?redirect=<原路由> - 登录成功后读取并校验
redirect - 合法则回跳,不合法则去默认页
场景 B:会话过期后访问受保护 API
- 请求命中 API 守卫
- 会话失效,返回
401 - 前端拦截到
401,清理登录态 - 若当前在受保护页面,触发跳转到登录页
场景 C:首页双态展示
- 首页属于白名单可访问
- 页面内部根据
loggedIn渲染访客态或用户态 - 刷新会话后自动切换显示
测试与验收标准
路由访问测试
- 未登录访问受保护页应跳登录并保留
redirect - 已登录访问登录/注册页应跳转到有效
redirect或默认页 - 白名单页面在未登录下可直接访问
API 鉴权测试
- 未登录访问受保护 API 返回
401 - 白名单 API 未登录可正常请求
- 过期会话访问受保护 API 返回
401
安全测试
redirect注入(http://...、//...、javascript:...)均被拦截- 白名单边界:
/login-xxx不应命中/login - 不出现登录页/注册页重定向死循环
体验测试
- 首页登录态切换稳定,不出现明显错误闪烁
- 登出后状态同步清空,页面表现与未登录一致
渐进实施顺序
- 增加路由规则常量与
redirect校验工具 - 落地前端全局中间件(先页面策略)
- 落地 API 守卫中间件(再后端兜底)
- 接入统一会话组合式函数,替换分散状态判断
- 补齐回归测试与手工验收清单
非目标(本次不做)
- 角色/权限点(RBAC)细粒度控制
- 多租户权限隔离
- OAuth、SSO 等第三方登录
- 动态远端下发白名单配置
风险与应对
- 风险:误将必要接口放入受保护导致前端初始化失败
应对:白名单初始集合最小化后,逐个验证关键页面 - 风险:新增页面忘记考虑权限属性
应对:默认拦截策略天然兜底,新增页面仅在确需公开时加入白名单 - 风险:重定向逻辑复杂导致跳转异常
应对:redirect工具函数集中实现,并覆盖异常值测试
设计结论
在当前项目阶段,采用“前端全局路由守卫 + 服务端 API 统一守卫”的登录优先模型,能以最小复杂度建立稳定安全边界,并保留后续扩展(认证辅助页、业务页拆分、角色权限)的空间。