Nova Forum 安全加固审查:CORS 与 JWT 的隐藏陷阱

发布于 2026-10-10 09:00 1080 字 6 min read AI 主笔 正文主要由 AI 起草,我负责挑选和改写。

暂无系列文章
-09-28 在 对 HEAD f177cdc 的未提交安全加固做只读审查,首次复现了两个隐藏问题:CORS allowedOrigins=[]+allowCredentials=true 抛异常,以及 JWT 有效但用户不存在时 UsernameNotFoundException 逃出过滤器**。把这次审查的过程和发现写下来。 Nova Forum 项目背景 Nova Forum...

2026-09-28 在 /home/cao/workspace/nova-forum 对 HEAD f177cdc 的未提交安全加固做只读审查,首次复现了两个隐藏问题:CORS allowedOrigins=[*]+allowCredentials=true 抛异常,以及 JWT 有效但用户不存在时 UsernameNotFoundException 逃出过滤器。把这次审查的过程和发现写下来。

Nova Forum 项目背景

Nova Forum 是一个技术论坛项目,Spring Boot + Vue,定位为求职学习项目。需求分析阶段已于 2026-09-11 冻结,文档:docs/需求分析.md,下一步进入系统设计阶段(数据库/API/模块划分)。

核心设计决策:

  • 角色:GUEST / USER / MODERATOR(按板块授权)/ SYS_ADMIN;废除「内容管理员」;版主由 SYS_ADMIN 直接分配
  • 认证:access JWT 15min 前端内存 + refresh 14d HttpOnly+Secure+SameSite=Lax Cookie;DB user_session 存 refresh 哈希;多设备
  • refresh rotation(旧 token 重用返回 401 REFRESH_TOKEN_INVALID,无 token family)
  • /auth/refresh 仅 POST,CSRF 靠 SameSite+Origin/Referer
  • 登录失败按 username_normalized+IP 15min 5 次单机内存限流
  • 封禁 ACTIVE/BANNED 临时/永久(banned_until/ban_reason),仅 SYS_ADMIN;封禁后登录/refresh 403 ACCOUNT_BANNED

复现的第一个问题:CORS 配置异常

现象:allowedOrigins=[*] 与 allowCredentials=true 同时设置时,Spring Security 直接抛异常。

根因:Spring Framework 的 CorsConfiguration 不允许 allowedOriginPatterns 为 * 同时 allowCredentials 为 true。这是 Spring 的安全设计——当允许凭据(Cookie)时,通配符 * 会被拒绝,必须显式列出允许的源。

修复:SecurityConfig 改成 app.cors.allowed-origins 可配置明确白名单(默认 localhost/127.0.0.1

/5173)。临时 Java probe 确认允许 Origin 预检 200、非白名单 403、缺失用户 401、正常用户进入下游并成功认证。

复现的第二个问题:UsernameNotFoundException 逃出过滤器

现象:JWT 有效(未过期)但用户不存在时,UsernameNotFoundException 直接逃出 JwtAuthenticationFilter,返回 500 而非 401。

修复:JwtAuthenticationFilter catch(Exception) 返回 JSON 401。

剩余 P2:JwtAuthenticationFilter.java:59 catch(Exception) 把数据库连接等基础设施故障也错误映射 401(probe 已复现 IllegalStateException 场景),应区分认证异常和 5xx。

验证与回归

二次 mvn verify 仍 299 项全过、JaCoCo 通过;nightly-report 记录 14 仍写 305 且 CORS 说明过时。git diff —check 报 JwtAuthenticationFilter.java:54 行尾空白。仓库源码未被审查代理修改。

修复质量评估 7.5/10:原两问题已修但异常分类、回归测试和报告准确性不足。

Nova Forum 安全设计要点总结

这次审查中看到的几个安全设计决策,值得记录:

认证与 Token 管理

  • access JWT 15min 存前端内存,refresh 14d HttpOnly+Secure+SameSite=Lax Cookie
  • DB user_session 存 refresh 哈希,支持多设备
  • logout 撤销当前 / logout-all 全部;改密/封禁撤销全部
  • 不做黑名单/token_version;不引入 Redis
  • refresh rotation:旧 token 重用返回 401 REFRESH_TOKEN_INVALID

密码与注册

  • 注册仅 username+password,无邮箱/验证码/邮件依赖,注册即自动登录
  • 密码 12–128 不强制组合、拒弱密码、不得含 username
  • Argon2id(BCrypt 处理 72 字节)
  • MVP 无密码找回

权限与状态三维

  • 状态三维:status(DRAFT/PUBLISHED/DELETED) / visibility(PUBLIC/MEMBERS_ONLY/PRIVATE) / moderation(NORMAL/HIDDEN/VIOLATION)
  • HIDE→HIDDEN;DELETE→DELETED(违规同时 VIOLATION)
  • visibility 仅作者设置
  • 判权顺序 DELETED→moderation→visibility;DELETED 含作者一律 404(走后台独立接口)

限流与安全

  • 登录失败按 username_normalized+IP 15min 5 次单机内存限流
  • 用户名不存在与密码错统一提示(不泄露用户是否存在)
  • 改密必须验旧密码
  • 发帖 30s、评论 5s(按用户 ID,应用层)

日志安全

  • 日志禁记密码/hash/token/密钥/Cookie/Authorization/用户正文
  • 配置 application.yml+dev/prod,secret 环境变量不入 Git

经验教训

  1. CORS 配置不能图省事用 *:只要涉及凭据传递(Cookie/Authorization Header),必须显式列出允许的源
  2. 过滤器层要 catch 所有异常并映射到 HTTP 状态码:不要让内部异常类(如 UsernameNotFoundException)直接逃出变成 500
  3. 区分基础设施故障和认证失败:数据库连接失败应返回 500/503,而不是 401 掩盖问题
  4. 安全审查要实际运行 probe 复现:只读 diff 看不出的问题,通过 Java probe 真实请求才复现出来
  5. CI 报告要及时更新:CORS 说明过时、nightly-report 数字不符这类问题,会在后续迭代中误导开发者

这次审查让我深刻体会到:安全加固不是改完一个 bug 就结束,而是要确保修复不会掩盖其他路径的同类问题。异常分类那项 P2 值得在下次迭代中专门处理。

执笔 AI 主笔

随机文章

加载中...