长文档建议按当前分组逐页阅读;右侧辅助栏会保留当前位置和快速操作。
Mini-HBUT OIDC 接入指南
面向第三方网站、服务端应用和原生客户端的统一接入说明。本页是 Mini-HBUT Identity 的唯一正式开发者文档入口; Developer Portal 只负责应用创建、审核状态、Redirect URI、Scope 与凭据生命周期管理。
mini_hbut_app,表示 Mini-HBUT App 基于用户本地学校登录状态完成验证; 它不是学校服务器直接向第三方签发的官方 OIDC 身份断言。请勿用于金融、考试身份核验等要求官方强实名的场景。1. 协议入口与 canonical issuer
OIDC 的 issuer 必须按字符串精确比较。中文域名只适合人类展示;协议配置、Discovery、iss 校验和 SDK 配置统一使用下面的 ASCII / Punycode canonical issuer。
https://id.xn--vhq74jc2fzpchter27a.com
| 能力 | 端点 |
|---|---|
| Discovery | https://id.xn--vhq74jc2fzpchter27a.com/.well-known/openid-configuration |
| Authorization | https://id.xn--vhq74jc2fzpchter27a.com/oauth/authorize |
| Token | https://id.xn--vhq74jc2fzpchter27a.com/oauth/token |
| JWKS | https://id.xn--vhq74jc2fzpchter27a.com/oauth/jwks |
| UserInfo | https://id.xn--vhq74jc2fzpchter27a.com/oauth/userinfo |
| Revocation | https://id.xn--vhq74jc2fzpchter27a.com/oauth/revoke |
| Logout | https://id.xn--vhq74jc2fzpchter27a.com/oauth/logout |
首选做法是让 OIDC SDK读取 Discovery,而不是在业务代码里长期硬编码各端点。V1 只开放 Authorization Code; 不提供 implicit/hybrid,也不宣告未实现的 PAR 动态入口。
2. 先在 Developer Portal 注册应用
前往 Mini-HBUT Developer Portal 创建应用。 V1 对外提供两种应用类型:
Web / Confidential
- 服务端安全持有
client_secret; - Token 端点使用
client_secret_basic; - 生产 Redirect URI 必须为 HTTPS;
- 即使有 secret,也仍强制使用 PKCE S256。
Native / Public
- 不签发、也绝不能内置
client_secret; - Token 端点认证方式为
none; - 支持自定义 scheme 与 127.0.0.1 / [::1] loopback;
- 必须使用 PKCE S256,并使用系统浏览器完成授权。
应用生命周期为 Draft → Pending Review → Approved → Active。只有 Active Client 可以进入授权流程。 Suspended / Revoked Client 会立即失去授权能力;Revoked 为终态。
3. Redirect URI 规则
- 授权请求中的
redirect_uri必须与已审核注册值精确匹配,不支持通配符、前缀、后缀或正则匹配。 - 禁止 fragment(
#...)、userinfo(user@host)和控制字符。 - Web / Confidential 生产地址只能使用 HTTPS;本地开发例外仅用于明确的 localhost 环境。
- Native custom scheme 不能使用 http/https;示例:
my-app:/oauth/callback。 - Native loopback 仅允许
http://127.0.0.1或http://[::1],可使用运行时动态端口。 - 不要把授权码、token、handoff secret 或其它凭据放进自己定义的 Redirect URI 静态参数。
4. Web 服务端接入
推荐使用成熟 OIDC SDK。下面以 openid-client 的典型服务端流程表示关键约束;示例中的所有 Client 值均为占位符。
import * as oidc from 'openid-client'
const issuer = new URL(process.env.MINI_HBUT_ISSUER!)
const clientSecret = process.env.MINI_HBUT_CLIENT_SECRET!
const config = await oidc.discovery(
issuer,
process.env.MINI_HBUT_CLIENT_ID!,
{
client_secret: clientSecret,
redirect_uris: ['https://your-app.example.com/oauth/callback'],
token_endpoint_auth_method: 'client_secret_basic',
},
oidc.ClientSecretBasic(clientSecret),
)
// 每次登录生成新的 verifier / state / nonce,并绑定到当前服务端会话。
const verifier = oidc.randomPKCECodeVerifier()
const challenge = await oidc.calculatePKCECodeChallenge(verifier)
const state = oidc.randomState()
const nonce = oidc.randomNonce()
const authorizationUrl = oidc.buildAuthorizationUrl(config, {
redirect_uri: 'https://your-app.example.com/oauth/callback',
response_type: 'code',
scope: 'openid profile',
code_challenge: challenge,
code_challenge_method: 'S256',
state,
nonce,
})
// 回调收到 code 后,在服务端完成授权码交换。
const tokens = await oidc.authorizationCodeGrant(config, currentUrl, {
pkceCodeVerifier: verifier,
expectedState: state,
expectedNonce: nonce,
})5. Native / 桌面 / 移动端接入
// Native/Public Client:没有 client_secret,仍然使用 Authorization Code + PKCE S256。 // 1. 生成 verifier / challenge / state / nonce // 2. 使用系统浏览器打开 authorization_endpoint // 3. 通过已经注册的 custom URI scheme 或 127.0.0.1 loopback 接收 code // 4. 使用同一个 verifier 调 token_endpoint 兑换 token GET https://id.xn--vhq74jc2fzpchter27a.com/oauth/authorize ?client_id=YOUR_NATIVE_CLIENT_ID &redirect_uri=my-app:/oauth/callback &response_type=code &scope=openid%20profile &state=RANDOM_STATE &nonce=RANDOM_NONCE &code_challenge=BASE64URL_SHA256_VERIFIER &code_challenge_method=S256
- Native App 是 Public Client:二进制中不能安全保存一个所有用户共用的 Client Secret。
- 外部系统浏览器负责展示授权页;不要用内嵌 WebView 收集身份认证凭据。
- 每次请求独立生成
code_verifier、state、nonce。 - 回调后同时校验 state、ID Token nonce、issuer、audience、有效期与签名。
- 自定义 scheme 可能被其它 App 抢占;能使用平台验证过的 App Link / Universal Link 时应优先使用。
6. Scope 与 Claims
openid基础OIDC 必选 scope。返回 ID Token 与 pairwise sub,用于确认“同一个 Mini-HBUT 账户”完成了登录。
profile基础返回允许展示的基础资料,如 name / preferred_username;不等同于学校官方身份。
student.identity敏感通过 UserInfo 返回学校身份快照与验证来源。申请时需要说明用途、隐私政策与联系方式,并经过管理员审核和用户授权。
offline_access敏感允许签发 Refresh Token。仅在确实需要长期会话时申请,并按高敏感凭据存储和轮换。
sub 是 pairwise subject,不是学号,也不保证跨不同第三方 Client 相同。第三方自己的账号关联应以当前 Client 下稳定的 sub 为主键,不要用姓名、学号快照或昵称代替 OIDC subject。
GET https://id.xn--vhq74jc2fzpchter27a.com/oauth/userinfo
Authorization: Bearer ACCESS_TOKEN
// 申请并获批 student.identity 后,UserInfo 可能包含:
{
"sub": "PAIRWISE_SUBJECT",
"name": "显示姓名",
"preferred_username": "显示姓名",
"hbut_student_id": "学校身份快照",
"hbut_student_name": "学校姓名快照",
"hbut_verification_method": "mini_hbut_app",
"hbut_verified_at": "2026-08-15T00:00:00.000Z"
}学校身份类字段只在申请并获批 student.identity 后通过 UserInfo 获取,平台刻意不把这些敏感字段塞进 ID Token, 以减少前端日志、Cookie 或第三方中间件无意长期保存的概率。
7. Mini-HBUT App Approval 实际发生什么
- 第三方应用把用户重定向到标准
/oauth/authorize。 - Identity Core 校验 Client、Redirect URI、Scope、PKCE 后创建短期 AuthRequest。
- 浏览器进入
auth.*接力页,只展示应用、域名、Scope 并尝试唤起 Mini-HBUT;也可以展示跨设备二维码。 - Mini-HBUT 从服务端重新读取 AuthRequest,不相信 Deep Link/QR 中携带的应用名称、Scope 或身份字段。
- 用户在 App 内确认允许/拒绝;App 使用本机设备 Ed25519 私钥对 request/challenge/client/scope/nonce 等上下文签名。
- Core 验证设备绑定、签名、nonce、时间窗、请求状态与一次性约束后推进授权状态。
- 浏览器接力页观察到批准后恢复 OIDC Interaction,最终由 OIDC Provider 把一次性 Authorization Code 返回已注册的第三方 callback。
Deep Link / QR 只是“把短期请求带到 App”的唤起通道,本身不是认证凭据;学校密码、学校 Cookie 和 CAS 会话不会发送给第三方应用。
8. Token、刷新与撤销
- Authorization Code 一次性使用;兑换失败或过期后重新发起登录,不重复使用旧 code。
- Access Token / ID Token 都必须按 Discovery/JWKS 验证 issuer、audience、签名与有效期。
- 需要 Refresh Token 时显式申请
offline_access;平台启用 Refresh Token rotation,旧 refresh token 不应重复使用。 - 令牌撤销使用
POST /oauth/revoke;退出 OIDC 会话使用 RP-Initiated Logout 端点。 - Client Secret 泄露后在 Developer Portal 立即轮换;旧 secret 失效后同步吊销相关会话/令牌并检查审计日志。
9. 常见错误
| 错误 | 通常含义 | 处理建议 |
|---|---|---|
invalid_request | 参数缺失、PKCE/state 等格式不符合要求 | 重新生成完整授权请求,不复用旧 code |
unauthorized_client | Client 类型、状态或流程不允许 | 确认应用已经审核并处于 Active |
invalid_redirect_uri | 回调与注册值不完全一致 | 逐字符核对 Developer Portal 中的 Redirect URI |
invalid_scope | 请求了未批准的 Scope | 删除未批准 Scope,或重新提交审核 |
access_denied | 用户在 Mini-HBUT 中拒绝了本次授权 | 尊重拒绝,不静默循环重新唤起 |
invalid_grant | code / refresh token 过期、已使用或绑定信息不匹配 | 创建一轮全新的授权流程 |
invalid_client | Confidential Client 认证失败 | 核对服务端 secret,必要时执行轮换 |
10. 上线前安全检查
- 使用 Discovery + 成熟 OIDC/OAuth SDK,不自己实现 JWT/签名/授权码解析。
- 所有 Client 都启用 PKCE S256;state、nonce、verifier 每次随机并绑定当前登录会话。
- Confidential secret 仅存在服务器 Secret Manager / 环境变量,不进入浏览器 bundle、移动 App、日志或 Git。
- 浏览器不长期保存 OIDC token;服务端使用 Secure / HttpOnly / SameSite Cookie 建立自己的会话。
- Redirect URI 使用最小集合;不开放通配、开放重定向或“任意 next URL”。
- 只申请业务真正需要的 Scope;尤其不要为了“以后可能用到”默认申请 student.identity / offline_access。
- 把
mini_hbut_app验证来源如实展示给需要身份保证判断的业务,不包装成“湖北工业大学官方认证”。 - 日志中禁止记录 Authorization header、token、client secret、授权码、PKCE verifier 或 App Approval handoff secret。
- 收到 Suspended/Revoked、invalid_client、refresh reuse 等信号后 fail closed,不用旧凭据继续尝试。
11. 规范与更多入口
相关文档
当前页面读完后,可以按下面的交叉链接继续。用户文档、开发者文档、故障排查、参考资料和历史文档会互相补充。