OIDC 集成指南

OIDC 集成指南

MLiev IAM 作为 OIDC 身份提供商(IdP),为第三方应用提供单点登录(SSO)服务。本文档说明如何将你的应用接入 MLiev IAM。

支持的功能

  • 授权码流程(Authorization Code Flow)
  • 隐式流程(Implicit Flow)
  • 客户端凭据流程(Client Credentials Flow)
  • 刷新令牌(Refresh Token)
  • PKCE(Proof Key for Code Exchange)
  • 用户信息端点(UserInfo)
  • 令牌内省(Token Introspection)
  • 令牌撤销(Token Revocation)
  • OIDC 发现文档
  • JWKS 端点
  • RS256 / HS256 签名

协议端点

所有 OIDC 端点以 /oidc/:client_id 为前缀:

端点 方法 路径
发现文档 GET /oidc/:client_id/.well-known/openid-configuration
授权 GET/POST /oidc/:client_id/authorize
令牌 GET/POST /oidc/:client_id/token
用户信息 GET/POST /oidc/:client_id/userinfo
令牌内省 POST /oidc/:client_id/introspect
令牌撤销 POST /oidc/:client_id/revoke
JWKS GET /oidc/:client_id/jwks

所有端点中的 :client_id 是 OIDC 客户端的 Client ID。

第一步:创建 OIDC 客户端

通过管理员 API 创建 OIDC 客户端:

curl -X POST http://localhost:8080/api/admin/applications \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <admin_token>" \
  -d '{
    "name": "我的应用",
    "description": "示例 OIDC 客户端",
    "protocolType": "oidc",
    "protocolConfig": {
      "redirectUris": ["http://localhost:3000/callback"],
      "grantTypes": ["authorization_code", "refresh_token"],
      "scopes": ["openid", "profile", "email"],
      "clientType": "confidential",
      "requirePkce": false
    }
  }'

响应中包含 clientIdclientSecret,请妥善保管。

第二步:授权码流程

2.1 引导用户到授权页面

将用户浏览器重定向到授权端点:

https://auth.example.com/oidc/{client_id}/authorize?
  response_type=code&
  client_id={client_id}&
  redirect_uri=http://localhost:3000/callback&
  scope=openid profile email&
  state=随机字符串&
  nonce=随机字符串

参数说明:

参数 必填 说明
response_type 固定为 code
client_id 客户端 ID
redirect_uri 回调地址(必须在客户端注册的列表中)
scope 请求的作用域(至少包含 openid
state 推荐 防 CSRF 的随机字符串
nonce 推荐 防重放的随机字符串

2.2 用户登录并授权

用户在 IAM 的登录页面完成认证和授权确认后,浏览器会重定向回你的回调地址:

http://localhost:3000/callback?code=授权码&state=随机字符串

2.3 用授权码交换令牌

在服务端使用授权码获取令牌:

curl -X POST https://auth.example.com/oidc/{client_id}/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code\
&code=授权码\
&redirect_uri=http://localhost:3000/callback\
&client_id={client_id}\
&client_secret={client_secret}"

响应:

{
  "access_token": "访问令牌",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "刷新令牌",
  "id_token": "eyJhbGciOiJSUzI1NiIs...",
  "scope": "openid profile email"
}

使用 PKCE(公开客户端推荐)

对于无法安全存储 client_secret 的客户端(如 SPA、移动应用),使用 PKCE 增强安全性:

生成 Code Verifier 和 Challenge

// 生成 code_verifier(43-128字符的随机字符串)
const codeVerifier = generateRandomString(64);

// 计算 code_challenge
const codeChallenge = base64url(sha256(codeVerifier));

授权请求

/oidc/{client_id}/authorize?
  response_type=code&
  client_id={client_id}&
  redirect_uri=http://localhost:3000/callback&
  scope=openid profile email&
  code_challenge={code_challenge}&
  code_challenge_method=S256

令牌请求

curl -X POST /oidc/{client_id}/token \
  -d "grant_type=authorization_code\
&code=授权码\
&redirect_uri=http://localhost:3000/callback\
&client_id={client_id}\
&code_verifier={code_verifier}"

获取用户信息

使用 access_token 获取当前用户信息:

curl https://auth.example.com/oidc/{client_id}/userinfo \
  -H "Authorization: Bearer {access_token}"

响应:

{
  "sub": "123",
  "name": "张三",
  "preferred_username": "zhangsan",
  "email": "zhangsan@example.com",
  "email_verified": true
}

支持的 Scope 和返回字段:

Scope 返回字段
openid sub
profile name, preferred_username, nickname, picture
email email, email_verified
phone phone_number, phone_number_verified

客户端凭据流程

用于服务间认证(不涉及用户):

curl -X POST https://auth.example.com/oidc/{client_id}/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials\
&client_id={client_id}\
&client_secret={client_secret}\
&scope=api"

注意:客户端凭据流程不返回 refresh_tokenid_token

刷新令牌

curl -X POST https://auth.example.com/oidc/{client_id}/token \
  -d "grant_type=refresh_token\
&refresh_token={refresh_token}\
&client_id={client_id}\
&client_secret={client_secret}"

令牌内省

验证令牌是否有效(服务端使用):

curl -X POST https://auth.example.com/oidc/{client_id}/introspect \
  -d "token={access_token}\
&client_id={client_id}\
&client_secret={client_secret}"

令牌撤销

主动撤销令牌:

curl -X POST https://auth.example.com/oidc/{client_id}/revoke \
  -d "token={token}\
&client_id={client_id}\
&client_secret={client_secret}"

前端组件嵌入

MLiev IAM 提供可嵌入的登录组件,支持 iframe 方式集成:

iframe 嵌入登录页

<iframe
  src="https://auth.example.com/auth/login?session_id=xxx"
  width="400"
  height="500"
  frameborder="0"
></iframe>

<script>
window.addEventListener('message', (event) => {
  if (event.data.type === 'oidc-login-success') {
    console.log('登录成功:', event.data);
    // 处理登录成功回调
  }
});
</script>

页面路由

路径 说明
/auth/login?session_id={id} OIDC 登录页面
/auth/consent?session_id={id} 授权确认页面

OIDC 发现文档

客户端可通过发现文档自动获取所有端点配置:

curl https://auth.example.com/oidc/{client_id}/.well-known/openid-configuration

常见错误

错误 说明 解决方案
invalid_client 客户端 ID 或密钥错误 检查 client_id 和 client_secret
invalid_grant 授权码无效或已过期 授权码 10 分钟内有效,且一次性使用
invalid_scope 请求的作用域无效 确保包含 openid,且在客户端允许范围内
invalid_redirect_uri 回调地址不匹配 URI 必须完全匹配注册的 redirectUris

兼容标准

  • OpenID Connect Core 1.0
  • OAuth 2.0(RFC 6749)
  • Token Introspection(RFC 7662)
  • Token Revocation(RFC 7009)
  • PKCE(RFC 7636)