openid profile offline_access,若管理端 Client 未登记对应 scope,回调会出现
error=invalid_scope。详见下文「客户端集成说明」。
| 说明 | 地址 |
|---|---|
| JWKS | /oauth2/jwks |
| OAuth 回调示例路径 | /demo/callback |
| 管理端登录 | /admin/login/ |
| 管理端 UC 登录 | POST /user/login(tenantId=2,见 README) |
| 用户反馈页 | /oauth2/feedback(须带 client_id、email,见下文「用户反馈」) |
| OAuth 登录页 | /oauth2/login |
本 AS 基于 Spring Authorization Server,对外暴露标准 OAuth 端点;与常见 OIDC 客户端库的默认假设并不完全一致,集成前请先阅读本节。
| 能力 | 本发行版 |
|---|---|
| 授权码 + PKCE | 主路径(桌面 / SPA / 移动公开客户端) |
POST /oauth2/token、GET /oauth2/jwks、POST /oauth2/revoke | OAuth 2.0 标准端点 |
refresh_token | OAuth 2.0 refresh_token grant;管理端创建 Client 时默认启用(access 30 分钟 / refresh 7 天 / 轮换)。不依赖 OIDC 的 offline_access scope |
OIDC(openid、id_token、UserInfo) | 已验收(US-016):GET /.well-known/openid-configuration、GET /oauth2/userinfo;Client 须登记 openid(及所需 profile/email/phone) |
| access_token | JWT(HS256),含 tenant_id、user_code、role_codes 等项目自定义 claim,供 RS @RequireScope / @RequireRole 使用 |
?error=invalid_scope。openid、profile、offline_access。openid:仅在使用 OIDC / 需要 id_token 时按需登记;不是授权码流程的必选项。profile、offline_access:OIDC 常见 scope,均为可选;未登记却请求 → invalid_scope。scope 填 Client 已登记的角色名即可,不必照搬 OIDC 库默认三件套。+),不要用逗号。authorize 的 scope 只能是该 Client 在管理端已登记 scope 的子集。集成前请与OAuth Client 登记人(管理端 → OAuth 客户端)确认下表所列名称后再编码到客户端。
管理端创建 Client 时,「授权 Scope」下拉来自客户端租户(默认 tenantId=1)下已定义的角色名称。authorize 应携带与 RS 业务 API @RequireScope 一致的角色 scope。
| 说明 | 示例 | 备注 |
|---|---|---|
| 客户端租户自定义角色 | purchase-user、vip 等 |
以管理端「角色管理」与 OAuth Client 登记为准,无全局固定列表 |
| 与 RS 验权对齐 | RS 接口标注 @RequireScope("orders:read") 时,Client 须登记且 authorize 携带 orders:read |
业务 scope 字符串由项目约定,同样须预先登记 |
协作建议:Client 登记人导出该 Client 的「授权 Scope」列表 → 客户端开发按列表配置 authorize;不要自行假设 OIDC 默认 scope 已登记。
下列为 OIDC / OAuth 生态常见 scope 名;仅当管理端已为该 Client 勾选/登记时才能在 authorize 中请求。
| Scope | 类型 | 何时携带 | 本发行版说明 |
|---|---|---|---|
openid |
OIDC | 需要 id_token、走 OIDC 身份层时 |
部分支持,按需登记;token 响应可含 id_token |
profile |
OIDC | 需要基本资料类 claim 时 | 可选;UserInfo GET /oauth2/userinfo(Bearer access_token,须含 openid scope) |
email |
OIDC | 需要邮箱 claim 时 | 可按需登记;consent 页无专用中文描述时使用默认文案 |
address、phone |
OIDC | 需要地址/电话 claim 时 | 同上,按需登记 |
offline_access |
OIDC 惯例 | OIDC 客户端库要求显式申请 refresh 时 | 非必须:本 AS 默认已通过 OAuth 2.0 refresh_token grant 发 refresh,不依赖此 scope |
| 场景 | authorize scope 示例 | 管理端 Client 须登记 |
|---|---|---|
| 桌面/业务客户端(推荐) | {角色名} 或 {角色名} orders:read |
与示例中每一项一致 |
业务 + 需要 id_token |
openid {角色名} |
openid + 对应角色/业务 scope |
| OIDC 客户端库默认三件套 | openid profile offline_access |
三项全部登记;否则 invalid_scope |
| 仅需 access_token + refresh(本项目默认) | {角色名}(不必带 offline_access) |
角色 scope + Client 默认启用 refresh grant 即可 |
access_token(JWT)除 scope 外还含 role_codes(用户在该租户下的实际角色),RS 上 @RequireRole 看 role_codes,@RequireScope 看 token 中的 scope;两者可能同时存在,集成时请与后端约定。
redirect_uri,勾选需要的「授权 Scope」。scope 与上一步登记完全一致(或为其子集)。/oauth2/authorize?... → 登录(/oauth2/login)→ 按需 consent → 回调带 code。POST /oauth2/token(grant_type=authorization_code + PKCE)换取 access_token(及默认启用的 refresh_token;scope 含 openid 时另有 id_token)。GET /.well-known/openid-configuration 读取 Discovery;GET /oauth2/userinfo 携带 Authorization: Bearer {access_token} 获取身份 Claims。curl -s http://127.0.0.1:8080/.well-known/openid-configuration curl -s http://127.0.0.1:8080/oauth2/userinfo \ -H "Authorization: Bearer ACCESS_TOKEN" \ -H "Accept: application/json"
UserInfo 要求 access_token 授权 scope 含 openid;profile/email/phone claims 须对应 scope 已登记且 authorize 已携带。
GET /oauth2/authorize
?response_type=code
&client_id={管理端创建的 client_id}
&redirect_uri={须与 Client 登记完全一致,需 URL 编码}
&scope={与 Client 登记的 scope 一致,空格分隔}
&state={随机串}
&code_challenge={S256 挑战码}
&code_challenge_method=S256
发行版不预置演示 Client。请先在管理端 OAuth 客户端 中新建 PKCE 公共客户端,redirect_uri 可填示例:
http://127.0.0.1:8080/demo/callback,再按上文拼接 /oauth2/authorize 链接进行验收。
业务 App 在用户本地已登录后,携带 client_id 与 email 跳转至 AS 内置反馈页,无需在授权服务器再次登录即可提交文字与图片。提交成功后按 OAuth Client 配置的「反馈通知邮箱」异步发信;管理端可只读查阅历史反馈。
GET /oauth2/feedback?client_id={clientId}&email={urlEncodedEmail}&returnUrl={可选}
email 由业务 App 从本地登录态读取并自行 URL 编码(如 encodeURIComponent)。示例(将 {client_id} 换为管理端已创建的 Client ID):
http://127.0.0.1:8080/oauth2/feedback?client_id={client_id}&email=user%40example.com&returnUrl=http%3A%2F%2F127.0.0.1%3A8080%2Fdemo%2Fcallback
/oauth2/login。returnUrl 可选;非空时其 scheme + authority 须与 Client 任一 redirect_uri 一致,否则忽略(不展示返回链接,不阻断提交)。POST /file/upload,将响应中的文件名随 POST /oauth2/feedback 表单提交(字段 imageUrls)。spring.mail.*。未配置邮箱或 SMTP 时反馈仍落库,仅跳过发信。管理端默认菜单.csv,侧栏打开 用户反馈(/client/feedback);API 为 POST /manage/feedback/list、POST /manage/feedback/detail/{id}(只读)。管理端在侧栏 站内信 发信后,业务 App 持 OAuth access_token 定时拉取本渠道未读消息,并在用户阅读/作答后一次性消费(ack)。按 OAuth client_id 渠道可见;本期仅场景二。
POST /oauth2/message/pull
Authorization: Bearer {access_token}
Content-Type: application/json
{"clientId":"{client_id}","limit":20}
POST /oauth2/message/ack
Authorization: Bearer {access_token}
Content-Type: application/json
{"clientId":"{client_id}","messageId":"...","answers":[
{"questionId":"q1","optionIndexes":[0]},
{"questionId":"q2","optionIndexes":[0,2]},
{"questionId":"q3","text":"补充说明"}
]}
BaseResponse)业务体在 data;HTTP 200 仍可能业务失败(看 code)。鉴权失败为 401(无 JSON 体或由容器返回)。
{
"code": 0,
"message": "success",
"data": { ... }
}
data{
"items": [
{
"messageId": "msg-…",
"createTime": "2026-07-26T12:00:00.000+08:00",
"expireAt": "2026-08-25T12:00:00.000+08:00",
"blocks": [
{ "type": "TEXT", "body": "维护通知…" },
{ "type": "SINGLE", "id": "q1", "title": "是否收到?", "options": ["是", "否"] },
{ "type": "MULTI", "id": "q2", "title": "常用功能", "options": ["下单", "售后"] },
{ "type": "TEXT_REPLY", "id": "q3", "title": "补充", "maxLength": 500 }
]
}
]
}
| 字段 | 说明 |
|---|---|
items | 本渠道未 ack、未过期、SENT 消息,按 createTime 升序;可为空数组 |
messageId | 站内信 ID;ack 时回传 |
createTime / expireAt | 创建时间 / 过期时间(过期后不再下发) |
blocks[].type | TEXT | SINGLE | MULTI | TEXT_REPLY |
blocks[].body | 仅 TEXT:正文 |
blocks[].id / title | 题目 ID(ack 的 questionId)与题干;题型必有 |
blocks[].options | SINGLE/MULTI 选项文案;作答填 optionIndexes(0-based) |
blocks[].maxLength | 可选;TEXT_REPLY 文本上限 |
datadata 为 null(或省略);以 code == 0 表示成功。重复 ack 幂等成功(同渠道已消费不再改答案)。
code != 0)clientId 空、与 JWT aud 不一致、令牌缺声明、messageId 空等。com.mfs.user.message.enabled=false 时返回业务错误。answers 但不完整/不合法等(见响应 message)。clientId 必须等于 JWT aud(换 token 时的 OAuth client_id)。SENT)的信(含 blocks);建议轮询间隔由 App 自定(如 30s~2min)。answers 可选——省略或 [] 仅标记已读(不落答案);若携带非空 answers 则须一次覆盖全部题目;成功后同渠道不再下发;重复 ack 幂等成功。expireAt;过期未读不再出现,避免久未登录刷屏。/client/site-message)。{state=ewNV-utBxHHRLzvj0lt1Fw, code_verifier=496EOpAv8iaPx4mvq9ey_fkwXsCQd7V98Mr8k7AJ1jk, code_challenge=75kj6NNPpEXgnHAQiM4pCUCqwCFel4-nS3uE95KMrpk}
curl -X POST http://127.0.0.1:8080/user/login \
-H "Content-Type: application/json" \
-d '{"tenantId":"2","userCode":"admin","password":"123456"}'
sql/03-tenant-seed.sql 后使用 admin / 123456(tenantId=2)admin-ui,见 admin-ui/README.md