跳到正文
开发者文档/OIDC 接入指南

长文档建议按当前分组逐页阅读;右侧辅助栏会保留当前位置和快速操作。

开发者文档 · Identity

Mini-HBUT OIDC 接入指南

面向第三方网站、服务端应用和原生客户端的统一接入说明。本页是 Mini-HBUT Identity 的唯一正式开发者文档入口; Developer Portal 只负责应用创建、审核状态、Redirect URI、Scope 与凭据生命周期管理。

身份保证边界: Mini-HBUT 是第三方学生开发工具,不是湖北工业大学官方统一身份认证服务。 当前学校身份验证来源为 mini_hbut_app,表示 Mini-HBUT App 基于用户本地学校登录状态完成验证; 它不是学校服务器直接向第三方签发的官方 OIDC 身份断言。请勿用于金融、考试身份核验等要求官方强实名的场景。

1. 协议入口与 canonical issuer

OIDC 的 issuer 必须按字符串精确比较。中文域名只适合人类展示;协议配置、Discovery、iss 校验和 SDK 配置统一使用下面的 ASCII / Punycode canonical issuer。

https://id.xn--vhq74jc2fzpchter27a.com
能力端点
Discoveryhttps://id.xn--vhq74jc2fzpchter27a.com/.well-known/openid-configuration
Authorizationhttps://id.xn--vhq74jc2fzpchter27a.com/oauth/authorize
Tokenhttps://id.xn--vhq74jc2fzpchter27a.com/oauth/token
JWKShttps://id.xn--vhq74jc2fzpchter27a.com/oauth/jwks
UserInfohttps://id.xn--vhq74jc2fzpchter27a.com/oauth/userinfo
Revocationhttps://id.xn--vhq74jc2fzpchter27a.com/oauth/revoke
Logouthttps://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.1http://[::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,
})
授权码交换、Client Secret、Access Token、Refresh Token 和 ID Token 都应留在服务端。浏览器最好只拿到你自己站点的 Secure + HttpOnly + SameSite 会话 Cookie,不要把 OIDC token 写入 localStorage/sessionStorage。

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_verifierstatenonce
  • 回调后同时校验 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 实际发生什么

  1. 第三方应用把用户重定向到标准 /oauth/authorize
  2. Identity Core 校验 Client、Redirect URI、Scope、PKCE 后创建短期 AuthRequest。
  3. 浏览器进入 auth.* 接力页,只展示应用、域名、Scope 并尝试唤起 Mini-HBUT;也可以展示跨设备二维码。
  4. Mini-HBUT 从服务端重新读取 AuthRequest,不相信 Deep Link/QR 中携带的应用名称、Scope 或身份字段。
  5. 用户在 App 内确认允许/拒绝;App 使用本机设备 Ed25519 私钥对 request/challenge/client/scope/nonce 等上下文签名。
  6. Core 验证设备绑定、签名、nonce、时间窗、请求状态与一次性约束后推进授权状态。
  7. 浏览器接力页观察到批准后恢复 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_clientClient 类型、状态或流程不允许确认应用已经审核并处于 Active
invalid_redirect_uri回调与注册值不完全一致逐字符核对 Developer Portal 中的 Redirect URI
invalid_scope请求了未批准的 Scope删除未批准 Scope,或重新提交审核
access_denied用户在 Mini-HBUT 中拒绝了本次授权尊重拒绝,不静默循环重新唤起
invalid_grantcode / refresh token 过期、已使用或绑定信息不匹配创建一轮全新的授权流程
invalid_clientConfidential 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. 规范与更多入口

推荐阅读路径

相关文档

当前页面读完后,可以按下面的交叉链接继续。用户文档、开发者文档、故障排查、参考资料和历史文档会互相补充。