Quick Start

1. Discovery

标准 OpenID Connect Discovery 端点(机器可读值,必须使用 canonical 形式):

GET https://id.xn--vhq74jc2fzpchter27a.com/.well-known/openid-configuration
{
  "issuer": "https://id.xn--vhq74jc2fzpchter27a.com",
  "authorization_endpoint": "https://id.xn--vhq74jc2fzpchter27a.com/oauth/authorize",
  "token_endpoint": "https://id.xn--vhq74jc2fzpchter27a.com/oauth/token",
  "jwks_uri": "https://id.xn--vhq74jc2fzpchter27a.com/oauth/jwks",
  "userinfo_endpoint": "https://id.xn--vhq74jc2fzpchter27a.com/oauth/userinfo"
}

协议字段(iss、aud、JWKS、token 校验)只使用 https://id.xn--vhq74jc2fzpchter27a.com; 中文域名(id.湖北工业大学.com)仅用于人类展示,禁止在协议校验中使用。

2. Web 应用(Authorization Code + PKCE,服务端持有 secret)

// 服务端(Node + openid-client v6),绝不能在前端做授权码交换
import {
  discovery, buildAuthorizationUrl, authorizationCodeGrant,
  randomPKCECodeVerifier, calculatePKCECodeChallenge, randomState, randomNonce,
} from 'openid-client'

const config = await discovery(
  new URL(process.env.ISSUER_URL),          // canonical ASCII issuer
  process.env.CLIENT_ID,                    // 你的 client_id
  { client_secret: process.env.CLIENT_SECRET, // secret 只存在服务端
    redirect_uris: ['https://your-app.com/oauth/callback'],
    token_endpoint_auth_method: 'client_secret_basic' },
)

// 1) 发起登录(PKCE S256;state/nonce/verifier 每次随机且与会话绑定)
const verifier = randomPKCECodeVerifier()
const state = randomState()
const nonce = randomNonce()
const authUrl = buildAuthorizationUrl(config, {
  scope: 'openid profile',                  // 需要时再加敏感 scope
  state,
  nonce,
  code_challenge: await calculatePKCECodeChallenge(verifier),
  code_challenge_method: 'S256',
})
res.redirect(authUrl)

// 2) 回调:openid-client 自动校验 state/nonce/iss/aud/exp + PKCE 换码
const tokenSet = await authorizationCodeGrant(config, currentUrl, {
  pkceCodeVerifier: verifier,
  expectedState: state,
  expectedNonce: nonce,
})
const claims = tokenSet.claims()            // sub 为 pairwise 标识,与学号无关

3. Native 应用(Public Client,PKCE S256)

// Native(无 secret,强制 PKCE S256;RFC 8252)
// 1) 打开系统浏览器
openBrowser(authUrl)   // https://<issuer>/oauth/authorize?client_id=...&redirect_uri=my-app:/oauth/callback&response_type=code&scope=openid&state=...&code_challenge=...&code_challenge_method=S256
// 2) 自定义 scheme 回调拿到 code 后,用 verifier 在服务端/自身交换 token
//    禁止把 client_secret 内置进 App

Native 自定义 scheme 与 loopback(127.0.0.1 动态端口)遵循 RFC 8252; redirect_uri 必须与注册值逐字符精确匹配

4. callback / state / nonce 注意事项

  • state:防 CSRF,必须随请求生成并在回调中严格校验;
  • nonce:防重放,校验 id_token 中的 nonce 声明;
  • code_verifier:只存在内存/会话,禁止写入 URL 或日志;
  • 授权码一次性:交换失败请重新发起登录,不要重试同一个 code。

5. 审核流程

创建应用(草稿)→ 提交审核 → 管理员批准(APPROVED)→ 正式启用(ACTIVE)→ 可在授权端点使用。被拒绝时按 Review 页反馈修改后重新提交。