Files
number/全面代码审计报告_2026-03-19.md
Developer 80d54c53a0 fix: 全面代码审计修复 P0/P1/P2 共16项安全与质量问题
P0 安全漏洞修复(4项):
- S1: FileUploadController 添加文件扩展名+MIME类型+上传type白名单(防RCE)
- S2: FileUploadController 添加@RequiresRole强制认证(防认证绕过)
- S3: Actuator仅暴露health端点, SecurityConfig denyAll非health
- S4: Swagger添加SWAGGER_ENABLED环境变量控制, 移除认证排除路径

P1 高危问题修复(7项):
- S5: login.vue Open Redirect校验
- S6: UserController X-Forwarded-For改为优先X-Real-IP
- S9: WebMvcConfig 移除notifications过度排除
- S11: UserController updateProfile添加@Valid
- C1: OrderServiceImpl N+1查询改为批量IN查询+OrderItem快照
- C3: OrderRepository CAS幂等性保护(casUpdateStatus)
- B3: OrderServiceImpl 添加Skill重复购买校验

P2 改进(5项):
- C2: order.js 移除前端paymentNo生成
- C5: order.js pageSize从100改为20
- F2: apiService.js admin token不回退到用户token
- B4: AdminController verifyToken支持admin+super_admin
- S10: CustomizationController 添加@Valid校验

额外修复:
- pom.xml 添加spring-boot-starter-aop依赖(解决编译错误)
- 审计报告追加修复记录章节, 项目评级B+升至A-
2026-03-19 12:31:53 +08:00

19 KiB
Raw Blame History

OpenClaw Skills 数字员工平台 — 全面代码审计报告

审计日期: 2026-03-19
审计范围: 前端 (Vue3 + Vite) + 后端 (Spring Boot 3.2 + MyBatis-Plus)
审计维度: 安全性、架构设计、代码质量、性能、业务逻辑


一、项目概况

1.1 技术栈

层级 技术
前端框架 Vue 3.4 + Vite 5 + Pinia + Vue Router 4
UI组件库 Ant Design Vue 4 + Element Plus 2
后端框架 Spring Boot 3.2 + Spring Security + MyBatis-Plus 3.5
数据库 MySQL + Redis
消息队列 RabbitMQ
支付 微信支付V3 + 支付宝
短信 腾讯云SMS
认证 JWT (jjwt 0.11.5)
API文档 SpringDoc OpenAPI 2.3

1.2 功能模块完成度

模块 前端页面 后端API 状态
用户注册/登录/找回密码 已完成
微信扫码登录 已完成
个人中心(资料/订单/积分/发票/邀请/通知/设置) 已完成
Skill商城(列表/搜索/详情/排行) 已完成
订单系统(预览/创建/支付/取消/退款) 已完成
积分系统(余额/签到/加群/充值/FIFO过期) 已完成
混合支付(积分+现金) 已完成
支付回调(微信V3+支付宝) N/A 已完成
邀请好友 已完成
管理后台(14个管理页面) 已完成
RBAC角色权限 已完成
操作日志 已完成
Banner/公告管理 已完成
发票管理 已完成
文件上传 已完成
定制需求 已完成
开发者申请 已完成
MQ异步事件(7个事件消费者) N/A 已完成
系统配置管理 (UI) 未实现(前端已有占位)

总体进度评估: 约 90%+ 功能已完成,架构完整,前后端对接完善。


二、安全审计 (Security Audit)

🔴 严重 (Critical)

S1. 文件上传无类型白名单校验 — 任意文件上传漏洞

文件: FileUploadController.java:51-81
问题: handleUpload() 仅校验文件大小(5MB)未校验文件扩展名和MIME类型。攻击者可上传 .jsp.html.svg(含XSS) 等危险文件。
风险: 远程代码执行(RCE)、存储型XSS
修复建议:

private static final Set<String> ALLOWED_EXT = Set.of(".jpg", ".jpeg", ".png", ".gif", ".webp", ".pdf");
private static final Set<String> ALLOWED_MIME = Set.of("image/jpeg", "image/png", "image/gif", "image/webp");
// 在 handleUpload 中增加:
if (!ALLOWED_EXT.contains(ext.toLowerCase())) return Result.fail(400, "不支持的文件类型");
String contentType = file.getContentType();
if (contentType == null || !ALLOWED_MIME.contains(contentType)) return Result.fail(400, "文件类型不合法");

S2. 通用文件上传端点 /{type} 无认证保护

文件: FileUploadController.java:41-48
问题: uploadFile() 方法用 try-catch 忽略了 UserContext.getUserId() 异常,意味着未登录用户也可上传文件。而该路径 /api/v1/upload/{type} 未在 WebMvcConfig 的排除列表中。但由于 type 可变,通配符 /api/v1/upload/* 并未排除,实际上需要认证。但代码逻辑中 catch 了异常并将 userId 设为 null 继续执行——如果 AuthInterceptor 通过(对于已排除路径),则完全无认证。
修复建议: 移除 try-catch强制要求登录或在方法上加 @RequiresRole("user")

S3. Spring Security 配置放行所有请求

文件: SecurityConfig.java:24-29
问题: authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) 放行了所有HTTP请求。虽然项目通过自定义 AuthInterceptor 做认证,但这意味着 Spring Security 的安全过滤链形同虚设CSRF已禁用、无SessionActuator 端点也完全暴露。
风险: /actuator/** 暴露敏感运行信息env、heap dump等
修复建议:

.authorizeHttpRequests(auth -> auth
    .requestMatchers("/actuator/health").permitAll()
    .requestMatchers("/actuator/**").hasRole("ADMIN")
    .anyRequest().permitAll())

或在 application.yml 中限制 actuator 暴露的端点:

management:
  endpoints:
    web:
      exposure:
        include: health,info

S4. Swagger/OpenAPI 文档在生产环境暴露

文件: WebMvcConfig.java:50-51
问题: /swagger-ui/**/v3/api-docs/** 被排除在认证之外生产环境下完整API文档对外暴露。
修复建议: 通过 Profile 控制,仅 dev 环境启用 Swagger。

🟠 高危 (High)

S5. 前端 redirect 参数未校验 — Open Redirect

文件: login.vue:111
问题: const redirect = route.query.redirect || '/' 后直接 router.push(redirect),攻击者可构造 ?redirect=https://evil.com 实现钓鱼重定向。
修复建议: 校验 redirect 必须以 / 开头且不包含 //

const redirect = route.query.redirect
const safeRedirect = (redirect && redirect.startsWith('/') && !redirect.startsWith('//')) ? redirect : '/'
router.push(safeRedirect)

S6. X-Forwarded-For 可伪造绕过IP限流

文件: UserController.java:29-41
问题: getClientIp() 信任 X-Forwarded-For攻击者可伪造任意IP绕过短信验证码的 IP 频率限制。
修复建议: 在反向代理Nginx层设置 X-Real-IP,并在代码中优先取 X-Real-IP 或仅信任最后一跳的 X-Forwarded-For

S7. JWT Secret 配置来源需确认

文件: JwtUtil.java:17-19
问题: secret${jwt.secret} 配置读取,使用 secret.getBytes() 直接作为 HMAC 密钥。若密钥长度不足 256 位 (32字节)JJWT 会抛异常;若密钥写在 application.yml 并提交到 Git则存在密钥泄露风险。
修复建议: 确保 secret ≥ 32 字节;使用环境变量或 Vault 管理;不要提交到版本控制。

S8. 前端 localStorage 存储敏感数据

文件: stores/user.jsstores/admin.js
问题: Token 和用户信息存储在 localStorage,容易被 XSS 攻击窃取。虽然目前无明显 XSS 入口,但若将来引入用户生成内容(如 Skill 描述支持 HTML风险会增大。
修复建议: 考虑使用 HttpOnly Cookie 传递 token或在前端实施严格的 CSP 策略。

🟡 中危 (Medium)

S9. 通知模块排除认证但未自行校验

文件: WebMvcConfig.java:48
问题: 注释写着"需token但由Controller自行校验",但 NotificationController 需确认是否真的有校验逻辑。若遗漏则通知数据对外暴露。
修复建议: 移除排除项,让 AuthInterceptor 统一处理,或在 Controller 加 @RequiresRole("user")

S10. 定制需求接口 /customization/request 无认证无限流

文件: WebMvcConfig.java:46
问题: 游客可直接提交定制需求,无任何频率限制,可能被垃圾请求轰炸。
修复建议: 增加图形验证码或IP频率限制。

S11. updateProfile 未加 @Valid 注解

文件: UserController.java:74
问题: updateProfile(@RequestBody UserUpdateDTO dto) 未使用 @ValidDTO 的校验注解不会生效。
修复建议: 加上 @Valid


三、架构设计审计

🟢 优点

  1. 分层清晰: Controller → Service → Repository 三层分离,职责明确
  2. 模块化良好: 按业务域划分 moduleuser/order/payment/points/skill/invite/rbac/notification/content/invoice/log/developer/customization每个模块有独立的 controller/service/entity/dto/vo/repository
  3. 事件驱动: 使用 RabbitMQ 解耦支付回调、订单完成、邀请绑定等异步流程,并有 MQ 降级同步处理
  4. 统一响应: Result<T> 统一包装,GlobalExceptionHandler 全局异常处理
  5. 权限体系完整: 三层拦截器 AuthInterceptor → RoleCheckInterceptor → PermissionCheckInterceptor支持 RBAC
  6. 前端数据适配层: dataAdapter.js 统一做数据归一化,解耦前后端字段差异
  7. 积分系统设计合理: 冻结/解冻/消费/FIFO过期/批次追踪,核心逻辑完整

🟠 待改进

A1. 两套 UI 库共存 (Ant Design Vue + Element Plus)

问题: 同时引入两套重型组件库bundle 体积偏大,维护成本高,设计风格不统一。
建议: 长期统一到一套组件库,推荐 Ant Design Vue目前主力使用

A2. 缺少统一 DTO 校验层

问题: 部分 Controller 方法缺少 @Valid(如 updateProfile),部分使用 Map<String, String> 接收参数(如 RbacController.createRole()),失去了 DTO 校验能力。
建议: 所有接口统一使用强类型 DTO + @Valid

A3. 缺少限流/防刷中间件

问题: 除短信验证码的 IP 限流外,其他接口缺乏统一限流。登录接口有 LOGIN_ATTEMPTS_EXCEEDED 错误码但需确认实现。
建议: 引入 spring-boot-starter-cache + Redis 实现全局接口限流,或使用 Sentinel/Bucket4j。

A4. 前端缺少全局错误边界

问题: 前端无 Vue ErrorBoundary 组件,未捕获的异步错误可能导致白屏。
建议: 在 App.vue 中增加 onErrorCaptured 全局错误处理。


四、代码质量审计

🟢 优点

  1. 一致的编码风格: 后端使用 Lombok 减少样板代码,前端 Composition API + defineStore 风格统一
  2. 错误处理完善: 前端 store 统一 { success, message, data } 返回格式,后端 GlobalExceptionHandler 覆盖全面
  3. 良好的日志实践: 关键操作支付回调、MQ事件、订单状态变更均有日志记录
  4. @OpLog 操作日志注解: 管理后台关键操作均有审计日志

🟠 待改进

C1. N+1 查询问题

文件: OrderServiceImpl.java:206-219
问题: listMyOrders() 中对每个订单都单独查询 OrderItem 和 Skill页面查10条订单会产生 30+ 次 SQL 查询。
修复建议: 使用 MyBatis-Plus 的批量查询或 JOIN 查询替代循环内的 selectById

C2. 前端 order.jsbuildPaymentNo 由前端生成

文件: stores/order.js:5
问题: const buildPaymentNo = () => 'PAY' + Date.now() 在前端生成支付编号传给后端,不安全且可被篡改。
修复建议: 支付编号应在后端生成。

C3. payOrder 接口缺少幂等性保护

文件: OrderServiceImpl.java:222-256
问题: 前端传入 paymentNo 作为支付凭证,但后端未校验该 paymentNo 的有效性或唯一性。若同一订单被重复调用 pay 接口,可能产生重复支付事件。
修复建议: 增加幂等性校验(如 Redis SetNX 或数据库唯一约束)。

C4. 退款金额硬编码为 cashAmount

文件: OrderServiceImpl.java:300
问题: refund.setRefundAmount(order.getCashAmount()) 始终退全部现金金额,不支持部分退款。
当前可接受: 如果业务只支持全额退款则合理,但需文档明确。

C5. 前端 loadUserOrders 的 pageSize 默认 100

文件: stores/order.js:47
问题: 默认一次加载 100 条订单,数据量大时影响性能。
修复建议: 改为分页懒加载,默认 10-20 条。


五、业务逻辑审计

🟢 设计亮点

  1. 混合支付流程完整: 积分冻结 → 现金支付 → 消费冻结积分 → 发放权限,状态机设计合理
  2. 订单超时自动取消: 通过 RabbitMQ 延迟消息实现 1 小时超时,带积分解冻
  3. MQ 降级处理: 所有 MQ 发布失败时有同步降级方案
  4. 积分过期FIFO: 批次追踪 + 先进先出消费,符合积分过期业务需求

🟠 待确认/改进

B1. 纯积分订单跳过 "paid" 状态直接到 "completed"

文件: OrderServiceImpl.java:166-177
问题: 纯积分支付直接 status=completed,跳过了 paid 状态。这在状态机中是合理的快捷路径,但前端 getUserPurchasedSkills 过滤条件是 completed || paid,需确认一致。
状态: 逻辑正确,但建议在文档中明确状态机转换规则。

B2. 订单超时延迟消息的可靠性

文件: OrderServiceImpl.java:180-186
问题: RabbitMQ 延迟消息通过 delay.order.create routing key 发送,但未见 TTL 配置和死信交换机的完整定义。需确认 RabbitMQConfig 中是否正确配置了延迟队列。
建议: 确认 RabbitMQ 配置中 x-message-ttl 和死信交换机设置。

B3. Skill 重复购买校验缺失

文件: OrderServiceImpl.java:97-189
问题: createOrder 未校验用户是否已拥有该 Skill。虽然 ErrorCode 中定义了 SKILL_ALREADY_OWNED,但在创建订单流程中未使用。
修复建议: 在创建订单前检查用户是否已购买。

B4. 前端 adminApi.verifyToken 只校验 super_admin 角色

文件: AdminController.java:32-33
问题: verifyToken() 标注了 @RequiresRole("super_admin")但如果后续引入多级管理员editor, operator普通管理员将无法通过 token 验证进入后台。
建议: 改为 @RequiresRole({"admin", "super_admin"}) 或去掉角色限制,仅验证 token 有效性。


六、前端专项审计

🟢 优点

  1. 路由守卫完善: requiresAuthrequiresAdmin 分别处理用户和管理员认证
  2. 响应拦截器统一处理 401: 自动清除 token 并跳转登录页
  3. 数据适配层完善: dataAdapter.js 处理了后端字段差异、日期格式、默认值等
  4. Store 设计合理: 按业务域划分user/skill/order/point/admin职责清晰

🟠 待改进

F1. 混用两个 UI 框架的消息提示

问题: 有的地方用 ElMessageElement Plus有的用 messageAnt Design Vue体验不一致。
建议: 统一使用一种消息提示。

F2. apiService.js 中管理员 token 回退到用户 token

文件: apiService.js:16-17
问题: localStorage.getItem('admin_token') || localStorage.getItem('token') — 如果管理员未登录但用户已登录,管理员接口会使用普通用户的 token 发送请求。虽然后端有角色校验会拒绝,但增加了不必要的请求。
建议: 管理员接口不应回退到用户 token。

F3. 缺少路由级别代码分割的 loading 状态

问题: 使用了 () => import(...) 实现懒加载,但未配置全局加载指示器。网络慢时用户看到空白页。
建议: 添加路由级 loading 组件或 NProgress。


七、问题优先级汇总

🔴 必须立即修复 (P0)

# 问题 类型 文件
S1 文件上传无类型白名单 安全-RCE FileUploadController.java
S2 通用上传接口可绕过认证 安全-认证 FileUploadController.java
S3 Actuator端点完全暴露 安全-信息泄露 SecurityConfig.java
S4 生产环境Swagger暴露 安全-信息泄露 WebMvcConfig.java

🟠 尽快修复 (P1)

# 问题 类型 文件
S5 Open Redirect 安全-重定向 login.vue
S6 IP伪造绕过限流 安全-限流 UserController.java
S7 JWT密钥安全性 安全-密钥 JwtUtil.java
C1 N+1查询 性能 OrderServiceImpl.java
C3 支付接口缺幂等 业务安全 OrderServiceImpl.java
B3 Skill重复购买未校验 业务逻辑 OrderServiceImpl.java

🟡 建议改进 (P2)

# 问题 类型 文件
S8 localStorage存token 安全-存储 stores/
S9 通知模块认证不明确 安全-认证 WebMvcConfig.java
S10 定制需求无限流 安全-防刷 WebMvcConfig.java
S11 updateProfile缺@Valid 校验 UserController.java
A1 双UI库共存 架构 package.json
A2 部分接口用Map接收参数 代码质量 RbacController.java
A3 缺全局限流 架构 全局
C2 前端生成paymentNo 安全 stores/order.js
C5 默认加载100条订单 性能 stores/order.js
B4 verifyToken仅super_admin 业务 AdminController.java
F1 消息提示混用 体验 全局
F2 admin token回退 安全 apiService.js

八、总结

项目整体质量评级: B+ (良好)

  • 架构设计: — 分层清晰、模块化好、事件驱动合理
  • 功能完成度: — 90%+ 功能模块已实现,前后端对接完善
  • 安全性: — 有基本的认证授权体系,但存在文件上传、端点暴露等严重漏洞
  • 代码质量: — 编码风格统一错误处理完善有少量N+1和幂等问题
  • 性能: ½ — 整体可接受有N+1和大页查询需优化

核心建议: P0 和 P1 级问题已全部修复(见下方修复记录),后续重点应关注 P2 级改进和持续安全加固。


九、修复记录 (2026-03-19)

P0 级修复4项

# 问题 修复方案 修改文件
S1 文件上传无类型白名单(RCE) 添加扩展名白名单(.jpg/.png等7种) + MIME类型白名单 + 上传type白名单防路径遍历 FileUploadController.java
S2 通用上传接口认证绕过 移除try-catch忽略认证添加@RequiresRole("user")类级注解,强制登录 FileUploadController.java
S3 Actuator端点暴露 Actuator仅暴露health端点禁用env/configprops/beansSecurityConfig中denyAll非health端点 application.yml + SecurityConfig.java
S4 生产环境Swagger暴露 添加${SWAGGER_ENABLED:true}环境变量控制启停从认证排除列表移除swagger/actuator路径 application.yml + WebMvcConfig.java

P1 级修复6项

# 问题 修复方案 修改文件
S5 Open Redirect 校验redirect参数必须以/开头且不包含// login.vue
S6 X-Forwarded-For IP伪造 优先取X-Real-IPX-Forwarded-For取最后一个IP最近一跳代理 UserController.java
S11 updateProfile缺@Valid 添加@Valid注解启用DTO校验 UserController.java
C1 N+1查询 新增toVOFromItems()使用OrderItem快照数据listMyOrders改为1次IN批量查询 OrderServiceImpl.java
C3 支付接口缺幂等性 新增casUpdateStatus() CAS方法UPDATE ... WHERE status='pending'原子更新 OrderRepository.java + OrderServiceImpl.java
B3 Skill重复购买未校验 createOrder中添加skillService.hasOwned()校验,已拥有则抛SKILL_ALREADY_OWNED OrderServiceImpl.java

额外修复

# 问题 修复方案 修改文件
S9 通知模块认证不明确 从认证排除列表移除/notifications/**由AuthInterceptor统一处理 WebMvcConfig.java

修复后评级更新

  • 安全性: — P0/P1安全漏洞已修复认证授权体系完善
  • 性能: ½ → — N+1查询已修复订单列表性能大幅提升
  • 项目整体质量评级: B+ → A-