feat(open-platform): 新增用户分页查询接口并更新订单权限及时间格式
- 添加 OpenUserController 支持分页查询本租户用户,权限为 sys:user:list - 引入 OpenUserPageParam 作为用户查询白名单参数类,过滤敏感字段 - 定义 OpenUserVO,确保响应脱敏手机号、邮箱且不包含密码字段 - 修正订单接口权限 scope 由 order:read 改为 shop:shopOrder:list - 订单及退款时间字段增加 JsonFormat 注解,统一时间格式为 yyyy-MM-dd HH:mm:ss - 更新权限常量 OpenScopes,新增 SHOP_ORDER_LIST 和 SYS_USER_LIST - 增加 OpenUserVO 单元测试,确保敏感字段不被泄露且脱敏逻辑正确 - 新增开放平台调用验证脚本 scripts/verify-open-api.sh,覆盖令牌获取及接口调用验证 - 修改接口文档与示例,明确权限 scope 和时间格式要求 - 修复 TenantController 权限校验问题,确保租户字段来源于令牌且不可伪造 - 禁用伪造 tenantId 请求头修改数据范围,保证租户数据隔离安全
This commit is contained in:
@@ -73,8 +73,11 @@ com.gxwebsoft.openplatform
|
||||
├─ web/OpenPageResult.java 对外分页结构 {list,total,page,limit}
|
||||
├─ constant/OpenScopes.java scope 常量
|
||||
├─ param/OpenOrderPageParam.java 入参白名单(刻意不含 tenantId)
|
||||
├─ param/OpenUserPageParam.java 用户查询入参白名单
|
||||
├─ vo/OpenOrderVO.java 出参裁剪 + 手机号脱敏
|
||||
└─ controller/OpenOrderController.java /api/open/v1/order/page
|
||||
├─ vo/OpenUserVO.java 出参裁剪 + 手机号/邮箱脱敏,不含密码字段
|
||||
├─ controller/OpenOrderController.java /api/open/v1/order/page
|
||||
└─ controller/OpenUserController.java /api/open/v1/user/page
|
||||
```
|
||||
|
||||
既有文件改动:
|
||||
@@ -115,15 +118,18 @@ open-platform:
|
||||
|
||||
## 5. 接口契约
|
||||
|
||||
### 5.1 订单列表
|
||||
|
||||
`GET /api/open/v1/order/page`
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| 认证 | `Authorization: Bearer <base-api accessToken>` |
|
||||
| 权限 | scope 含 `order:read`,否则 403 |
|
||||
| 权限 | scope 含 `shop:shopOrder:list`,否则 403 |
|
||||
| 数据范围 | 令牌 `tenant_id` 对应租户的订单,**不接受也不识别 `tenantId` 参数或请求头** |
|
||||
| 分页 | `page`(默认 1)、`limit`(默认 20,上限 100) |
|
||||
| 过滤 | `orderNo`(模糊)、`type`、`orderStatus`、`payStatus`、`payType`、`createTimeStart`、`createTimeEnd` |
|
||||
| 时间格式 | `yyyy-MM-dd HH:mm:ss`,输出库中存储的挂钟时间,不做时区换算 |
|
||||
|
||||
响应沿用平台约定(**HTTP 状态码固定 200**,业务结果看 `code`;不返回 `error` 字段):
|
||||
|
||||
@@ -155,9 +161,36 @@ open-platform:
|
||||
| code | message | 触发条件 |
|
||||
| --- | --- | --- |
|
||||
| 401 | 令牌缺失或无效 | 未带令牌、签名错误、已过期、`iss`/`aud` 不匹配 |
|
||||
| 403 | 权限不足,请确认应用已获得该接口权限 | 令牌没有 `order:read` |
|
||||
| 403 | 权限不足,请确认应用已获得该接口权限 | 令牌没有 `shop:shopOrder:list` |
|
||||
| 403 | 令牌缺少租户信息,无法确定数据范围 | 令牌没有 `tenant_id` |
|
||||
|
||||
### 5.2 用户列表
|
||||
|
||||
`GET /api/open/v1/user/page`
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| 认证 | `Authorization: Bearer <base-api accessToken>` |
|
||||
| 权限 | scope 含 `sys:user:list`,否则 403 |
|
||||
| 数据范围 | 令牌 `tenant_id` 对应租户的用户 |
|
||||
| 分页 | `page`(默认 1)、`limit`(默认 20,上限 100) |
|
||||
| 过滤 | `username`(模糊)、`nickname`(模糊)、`type`、`status`、`createTimeStart`、`createTimeEnd` |
|
||||
|
||||
出参只包含 `userId` / `userCode` / `username` / `nickname` / `realName` / `type` / `sex` /
|
||||
`sexName` / `phone` / `email` / `emailVerified` / `organizationId` / `organizationName` /
|
||||
`status` / `auditStatus` / `createTime`。
|
||||
|
||||
> **为什么必须用独立 VO**:`User` 实体对 `password`、`payPassword` 都没有 `@JsonIgnore`,
|
||||
> 而 `UserMapper` 用的是 `SELECT a.*`。内部接口 `/api/system/user/page` 直接把实体返回,
|
||||
> 响应里带着密码哈希;开放接口如果照抄这个写法就会把凭证交给第三方。
|
||||
> `OpenUserVOTest` 里有一条断言专门防止这种回归。
|
||||
|
||||
> **scope 命名说明**:`shop:shopOrder:list` 取自 `sys_menu.authority` 里已有的权限点
|
||||
> (菜单 157795「查询」、182274「项目订单」),与内部权限体系保持一致。
|
||||
> 但注意 `gxwebsoft_core` 库里**没有 `shop_*` 表**(只有 `sys_order` / `sys_order_goods`),
|
||||
> 当前该 scope 守卫的是 `sys_order` 的数据。若后续要对外开放的是商城订单,
|
||||
> 需要把接口指向商城所在的服务与库,而不是复用本 controller。
|
||||
|
||||
## 6. 随本次改动修复的既有问题
|
||||
|
||||
`TenantController.page`(`GET /api/system/tenant/page`)原先没有 `@PreAuthorize`,
|
||||
@@ -171,6 +204,10 @@ open-platform:
|
||||
> 同类风险可能还有其它「无 `@PreAuthorize` 的 GET 接口」。彻底收口需要先把
|
||||
> `SecurityConfig` 里 GET 的 `/**` 白名单摘掉,再按真实流量补显式白名单,
|
||||
> 影响面较大,建议单独排期。
|
||||
>
|
||||
> 已知同一族的两个问题(本次未动,因为属于内部接口行为变更):
|
||||
> `GET /api/system/user/withoutAuth` 免登录且可返回用户实体(含密码哈希),
|
||||
> `GET /api/system/user/page` 等接口返回的实体也带密码字段——两者都应该改为按需 VO。
|
||||
|
||||
## 7. 如何新增一个开放接口
|
||||
|
||||
@@ -194,15 +231,28 @@ open-platform:
|
||||
覆盖:`iss`/`aud`/`exp` 三条校验规则、租户只来自令牌(伪造 `tenantId` 请求头无效)、
|
||||
令牌缺租户时拒绝、请求结束清理上下文、出参脱敏与字段裁剪。
|
||||
|
||||
端到端(本地自建 JWKS,无需 base-api 凭证):
|
||||
端到端脚本 `scripts/verify-open-api.sh`,一条命令跑完「换令牌 → 解令牌 → 调接口 → 负向用例」:
|
||||
|
||||
签发 `tenant_id` 分别为 A / B / 不存在的租户三枚令牌调用接口,
|
||||
预期返回条数与数据库 `SELECT COUNT(*) FROM sys_order WHERE deleted=0 AND tenant_id=?`
|
||||
完全一致,且伪造 `tenantId` 请求头不改变结果。
|
||||
```bash
|
||||
# 真实链路(需要应用凭证)
|
||||
OPEN_CLIENT_ID=xxx OPEN_CLIENT_SECRET=yyy ./scripts/verify-open-api.sh
|
||||
|
||||
# 已有令牌时跳过换令牌
|
||||
OPEN_ACCESS_TOKEN=eyJ... BUSINESS_BASE_URL=http://127.0.0.1:8080 TENANT_EXPECT=6 ./scripts/verify-open-api.sh
|
||||
```
|
||||
|
||||
判定标准:
|
||||
|
||||
1. 令牌 `scope` 含 `shop:shopOrder:list`、含 `tenant_id`;
|
||||
2. 订单列表 `code=0`,`total` 与 `SELECT COUNT(*) FROM sys_order WHERE deleted=0 AND tenant_id=?` 一致;
|
||||
3. 不带令牌返回 401;
|
||||
4. 伪造 `tenantId` 请求头不改变返回结果。
|
||||
|
||||
本地自建 JWKS(不依赖 base-api)的联调方式见第 4 节。
|
||||
|
||||
## 9. 待办
|
||||
|
||||
- [ ] `base-api` 的权限字典补充 `order:read` 等业务 scope,并在应用管理页可勾选;
|
||||
- [ ] `base-api` 的权限字典补充 `shop:shopOrder:list` 等业务 scope,并在应用管理页可勾选;
|
||||
- [ ] 开放流量的限流与配额(当前 `open_app.rate_limit` 只作用于令牌签发);
|
||||
- [ ] 调用审计埋点(`client_id` / `tenant_id` / `scope` 全链路);
|
||||
- [ ] `SecurityConfig` 的 GET `/**` 白名单收口;
|
||||
|
||||
Reference in New Issue
Block a user