# 兑换码入口防护 ## 本次变更 - 后台 `/manage/exchange/code/**` 增加类级后台登录校验;保留各接口权限。 - 为兼容当前前端,保留 GET `/createAndExport` 路径,但必须同时具有 `exchangeCode:add` 和 `exchangeCode:export`。后续前端联动再迁移为 POST。 - 生成入口在服务层限制为每次 1–500 个码、1–100 个规格,并检查类型枚举。 - 后台查询/导出只接收 `goodsId`、`skuId`、`code`、`userId`、`useStatus`、 `codeType`、`start`、`limit`;不再接收客户端 condition、操作符、逻辑分组及动态排序。 `code` 改为精确匹配。分页大小最多 100;导出最多 10000 条,且只导出平台类型码。 - root 判定改为精确角色匹配;非 root 仍按历史 operator 昵称限定范围, 空昵称或重名则拒绝查询。历史表没有管理员 ID 归属,本次不猜测或回填归属; 昵称变更后仍需管理员核实历史归属,后续应迁移到 operator_id。 - `/external/app/get/mirror` 和 `/external/app/refund/{id}` 仅允许后台 root, 分别要求 `exchangeCode:view`、`exchangeCode:refund`。禁止匿名第三方调用; 暂不支持第三方独立凭证或租户范围,接入方需迁移,不能恢复匿名访问。 - 外部查询不再透传请求参数;退款增加审计,并记录重发码操作者。 - 前台按兑换码查看详情只接受非空完整码,忽略客户端查询操作符。 - 未删除、修改任何生产兑换码;未改 GPT token 接口。 ## 发布 运行 `docs/sql/20261021-coupon-exchange-permissions.sql` 补齐 9 个权限码, 包含新增的 `exchangeCode:refund`。只自动授予 root,其他角色显式授权。 后台重新保存角色权限刷新缓存,重新登录验证。代码必须构建、发布才会生效。 ## 验收(尚未在线执行) 1. 匿名/仅前台 token 调用后台和 external 接口:拒绝,数据无变化。 2. 后台无相应权限:拒绝;只有 add 没有 export 不能调用 createAndExport。 3. external:普通后台角色即使有 view/refund 仍被拒绝,root 且有权限才允许。 4. 查询携带 condition、code-op、operator-op 等额外参数:不得改变结果范围。 5. 非 root 看不到其他唯一昵称创建者的码;重名/空昵称拒绝查询。 6. 数量 0、负数、501、空规格、未知类型均拒绝;合法请求可正常生成。 7. 分页超过 100 拒绝;导出超过 10000 条拒绝。 8. 前台详情缺少 code 或只有查询操作符不能返回兑换码。 回归测试命令(先修复现有腾讯云依赖缺失): ```powershell & 'D:\IntelliJ IDEA 2025.2.3\plugins\maven\lib\maven3\bin\mvn.cmd' -pl netflix-web -am test '-Dtest=ExchangeCodeSecurityTest,CouponWriteSecurityTest' '-Dsurefire.failIfNoSpecifiedTests=false' ``` ## 2026-10-21 用户端兑换码详情加固 - `GET /applets/user/get/ticket/code/detail` 只接受完整 code,缺失、空串、空白、非法字符和超过 256 字符均拒绝。兼容字母数字、下划线和连字符。 - 使用框架一次 URL 解码后的值,不再二次解码。改用 MyBatis 参数绑定的 code 等值查询,额外 skuIds/useStatus/排序/操作符不参与查询。 - 返回前用 Java 严格比较原码,避免数据库不区分大小写的匹配;响应不再包含 code 字段。前端应使用自己的输入码执行兑换,不依赖详情响应回填。 - 不修改兑换核销流程,不增加数据库迁移。此改动未新增限流,不代表全部兑换安全问题已完成。 - 部署验收:空码与错误码不返回数据;完整有效测试码正常展示;附加参数不能改变查询目标;响应不含完整兑换码。