OIDC 标准端点
Code Bird Cloud 实现了 OpenID Connect 1.0 和 OAuth 2.1 标准协议。本文档描述所有 OIDC 标准端点的使用方式。
OIDC 端点遵循标准协议规范,令牌使用 ES256(ECDSA P-256)算法签名。
发现端点
GET /.well-known/openid-configuration
返回 OpenID Connect 发现文档,包含所有 OIDC 端点的 URL 和服务器支持的特性。
请求示例:
curl -X GET https://your-domain/.well-known/openid-configuration成功响应:
{
"issuer": "https://your-domain",
"authorization_endpoint": "https://your-domain/oidc/authorize",
"token_endpoint": "https://your-domain/oidc/token",
"userinfo_endpoint": "https://your-domain/oidc/userinfo",
"revocation_endpoint": "https://your-domain/oidc/revoke",
"end_session_endpoint": "https://your-domain/oidc/end-session",
"jwks_uri": "https://your-domain/.well-known/jwks.json",
"scopes_supported": [
"openid",
"profile",
"email",
"phone",
"offline_access",
"urn:codebird:scope:organizations",
"urn:codebird:scope:organization_roles"
],
"response_types_supported": [
"code"
],
"response_modes_supported": [
"query",
"fragment"
],
"grant_types_supported": [
"authorization_code",
"refresh_token",
"client_credentials"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"ES256"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"none"
],
"code_challenge_methods_supported": [
"S256"
],
"claims_supported": [
"sub",
"iss",
"aud",
"exp",
"iat",
"name",
"email",
"email_verified",
"phone_number",
"phone_number_verified",
"picture",
"username",
"organizations",
"organization_roles",
"organization_id",
"organization_is_admin"
]
}JSON Web Key Set
GET /.well-known/jwks.json
返回当前所有活跃的签名公钥(JSON Web Key Set),用于验证 ID Token 和 Access Token 的签名。
请求示例:
curl -X GET https://your-domain/.well-known/jwks.json成功响应:
{
"keys": [
{
"kty": "EC",
"crv": "P-256",
"kid": "sk_001",
"x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
"use": "sig",
"alg": "ES256"
}
]
}说明: 依赖方应定期(建议每小时)刷新 JWKS 缓存,以获取最新的密钥信息。密钥轮换后,旧密钥仍会在一段时间内保留在 JWKS 中。
授权端点
GET /oidc/authorize
发起 OIDC 授权请求。此端点为浏览器重定向端点,不直接返回 JSON。
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
client_id | string | 是 | 应用的 Client ID |
redirect_uri | string | 是 | 授权回调地址(需在应用配置中注册) |
response_type | string | 是 | 固定为 code |
scope | string | 是 | 请求的权限范围(空格分隔) |
state | string | 推荐 | 防止 CSRF 的随机字符串 |
code_challenge | string | 推荐 | PKCE 挑战码(Base64url 编码的 SHA256 哈希) |
code_challenge_method | string | 推荐 | PKCE 方法,固定为 S256 |
prompt | string | 否 | 交互模式:login(强制登录)、consent(强制同意) |
支持的 Scope:
| Scope | 说明 |
|---|---|
openid | 必填,表示 OIDC 请求,响应中包含 ID Token |
profile | 获取用户基本信息(name、username、picture) |
email | 获取用户邮箱信息(email、email_verified) |
phone | 获取用户手机号信息(phone_number、phone_number_verified) |
offline_access | 请求 Refresh Token |
urn:codebird:scope:organizations | 获取用户所属的组织 ID 列表 |
urn:codebird:scope:organization_roles | 获取用户在各组织中的角色信息 |
请求示例:
https://your-domain/oidc/authorize?
client_id=cbc_app_x7k9m2n4p1&
redirect_uri=https://app.example.com/callback&
response_type=code&
scope=openid profile email offline_access&
state=random_state_string&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256成功行为:
浏览器会被重定向到登录页面。用户完成登录后,浏览器会被重定向到 redirect_uri,携带授权码:
https://app.example.com/callback?code=abc123&state=random_state_string错误行为:
如果参数有误,浏览器会被重定向到 redirect_uri,携带错误信息:
https://app.example.com/callback?error=invalid_request&error_description=Invalid+redirect_uri令牌端点
POST /oidc/token
用授权码、刷新令牌或客户端凭证交换访问令牌。
请求头:
Content-Type: application/x-www-form-urlencoded认证方式(二选一):
- Basic 认证:
Authorization: Basic base64(client_id:client_secret) - 请求体传参: 在请求体中包含
client_id和client_secret
授权码模式(Authorization Code)
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
grant_type | string | 是 | 固定为 authorization_code |
code | string | 是 | 授权码 |
redirect_uri | string | 是 | 需与授权请求中的一致 |
code_verifier | string | 条件必填 | PKCE 验证码(如果授权请求中使用了 code_challenge) |
client_id | string | 条件必填 | 客户端 ID(如果未使用 Basic 认证) |
client_secret | string | 条件必填 | 客户端密钥(如果未使用 Basic 认证,且非公共客户端) |
请求示例:
curl -X POST https://your-domain/oidc/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=abc123" \
-d "redirect_uri=https://app.example.com/callback" \
-d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" \
-d "client_id=cbc_app_x7k9m2n4p1" \
-d "client_secret=cbc_secret_a1b2c3d4e5f6g7h8i9j0"成功响应:
{
"access_token": "eyJhbGciOiJFUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_abc123def456...",
"id_token": "eyJhbGciOiJFUzI1NiIs...",
"scope": "openid profile email offline_access"
}刷新令牌模式(Refresh Token)
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
grant_type | string | 是 | 固定为 refresh_token |
refresh_token | string | 是 | 刷新令牌 |
organization_id | string | 否 | 组织 ID(获取组织级 Token) |
resource | string | 否 | 资源标识符(与 organization_id 配合使用) |
client_id | string | 条件必填 | 客户端 ID |
client_secret | string | 条件必填 | 客户端密钥 |
请求示例:
curl -X POST https://your-domain/oidc/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=rt_abc123def456..." \
-d "client_id=cbc_app_x7k9m2n4p1" \
-d "client_secret=cbc_secret_a1b2c3d4e5f6g7h8i9j0"成功响应:
{
"access_token": "eyJhbGciOiJFUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_new_xyz789...",
"id_token": "eyJhbGciOiJFUzI1NiIs...",
"scope": "openid profile email offline_access"
}客户端凭证模式(Client Credentials)
用于 MachineToMachine 类型应用的服务间通信。
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
grant_type | string | 是 | 固定为 client_credentials |
resource | string | 否 | 请求访问的资源标识符 |
scope | string | 否 | 请求的权限范围 |
organization_id | string | 否 | 组织 ID(获取组织级 Token) |
client_id | string | 条件必填 | 客户端 ID |
client_secret | string | 条件必填 | 客户端密钥 |
请求示例:
curl -X POST https://your-domain/oidc/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "resource=https://api.bookstore.com" \
-d "scope=read:books write:books" \
-d "client_id=cbc_app_m2m_001" \
-d "client_secret=cbc_secret_m2m_abc123"成功响应:
{
"access_token": "eyJhbGciOiJFUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read:books write:books"
}注意: 客户端凭证模式不返回
refresh_token和id_token。
令牌错误响应
{
"error": "invalid_grant",
"error_description": "Authorization code has expired or has been used"
}常见错误码:
| 错误码 | 说明 |
|---|---|
invalid_request | 请求参数缺失或格式错误 |
invalid_client | 客户端认证失败 |
invalid_grant | 授权码/刷新令牌无效或已过期 |
unauthorized_client | 客户端无权使用此授权类型 |
unsupported_grant_type | 不支持的授权类型 |
令牌撤销端点
POST /oidc/revoke
撤销访问令牌或刷新令牌。
请求头:
Content-Type: application/x-www-form-urlencoded请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
token | string | 是 | 要撤销的令牌 |
token_type_hint | string | 否 | 令牌类型提示:access_token 或 refresh_token |
client_id | string | 条件必填 | 客户端 ID |
client_secret | string | 条件必填 | 客户端密钥 |
请求示例:
curl -X POST https://your-domain/oidc/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=rt_abc123def456..." \
-d "token_type_hint=refresh_token" \
-d "client_id=cbc_app_x7k9m2n4p1" \
-d "client_secret=cbc_secret_a1b2c3d4e5f6g7h8i9j0"成功响应:
HTTP/1.1 200 OK根据 RFC 7009,无论令牌是否有效,撤销端点始终返回 200。
用户信息端点
GET /oidc/userinfo 或 POST /oidc/userinfo
获取当前认证用户的信息。返回的字段取决于授权时请求的 Scope。
/oidc/userinfo返回的是基于当前 Access Token 的标准 OIDC 用户 claims, 适合轻量用户展示与标准协议对接。 如果你需要实时的组织关系、应用信息或最新管理员状态,请改用GET /api/session/context。
请求头:
Authorization: Bearer <access_token>请求示例:
curl -X GET https://your-domain/oidc/userinfo \
-H "Authorization: Bearer eyJhbGciOiJFUzI1NiIs..."成功响应(scope: openid profile email phone):
{
"sub": "usr_abc123",
"username": "john_doe",
"name": "John Doe",
"picture": "https://example.com/avatar.png",
"email": "john@example.com",
"email_verified": true,
"phone_number": "+8613800138000",
"phone_number_verified": false
}Scope 与返回字段的对应关系:
| Scope | 返回字段 |
|---|---|
openid | sub |
profile | username、name、picture、gender、updated_at |
email | email、email_verified |
phone | phone_number、phone_number_verified |
urn:codebird:scope:organizations | organizations(用户所属组织 ID 数组) |
urn:codebird:scope:organization_roles | organization_roles(各组织角色信息,格式为 ["org_id:role_name"]) |
当 Access Token 具有组织上下文(例如授权请求或 refresh token 换取组织级 Token 时指定了 organization_id), /oidc/userinfo 还会额外返回:
| 字段 | 类型 | 说明 |
|---|---|---|
organization_id | string | 当前组织上下文的组织 ID |
organization_is_admin | boolean | 当前用户在该组织下是否为组织管理员 |
示例:
{
"sub": "usr_abc123",
"username": "john_doe",
"organization_id": "org_123",
"organizations": ["org_123"],
"organization_roles": ["org_123:member"],
"organization_is_admin": true
}说明:
organization_roles当前标准格式为字符串数组,每项格式为org_id:role_nameorganization_roles只表示角色模板名称,不再代表组织管理员身份- 当前登录用户在当前组织下是否为组织管理员,请读取
organization_is_admin
错误响应(令牌无效):
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="The access token is expired or invalid"结束会话端点
GET /oidc/end-session
结束用户的登录会话(登出)。
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id_token_hint | string | 推荐 | 用户的 ID Token,用于确定要登出的会话 |
post_logout_redirect_uri | string | 否 | 登出后的重定向地址 |
state | string | 否 | 状态参数,会原样传回到重定向地址 |
请求示例:
https://your-domain/oidc/end-session?
id_token_hint=eyJhbGciOiJFUzI1NiIs...&
post_logout_redirect_uri=https://app.example.com&
state=logout_state成功行为:
浏览器重定向到指定的 post_logout_redirect_uri:
https://app.example.com?state=logout_stateID Token 结构
ID Token 是一个 JWT,包含以下声明(Claims):
{
"iss": "https://your-domain",
"sub": "usr_abc123",
"aud": "cbc_app_x7k9m2n4p1",
"exp": 1718500800,
"iat": 1718497200,
"at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q",
"name": "John Doe",
"username": "john_doe",
"email": "john@example.com",
"email_verified": true,
"picture": "https://example.com/avatar.png"
}标准声明:
| 声明 | 说明 |
|---|---|
iss | 签发者(Issuer),即 Code Bird Cloud 的 Base URL |
sub | 主体(Subject),即用户 ID |
aud | 受众(Audience),即 Client ID |
exp | 过期时间(Unix 时间戳) |
iat | 签发时间(Unix 时间戳) |
at_hash | Access Token 的哈希值 |
集成指南
推荐的授权流程(PKCE + Authorization Code)
1. 生成 code_verifier(随机字符串,43-128 字符)
2. 计算 code_challenge = BASE64URL(SHA256(code_verifier))
3. 重定向到 /oidc/authorize(携带 code_challenge)
4. 用户完成登录
5. 获取授权码(从回调 URL 中提取 code 参数)
6. POST /oidc/token 交换令牌(携带 code_verifier)
7. 验证 ID Token(使用 JWKS 公钥验证 ES256 签名)
8. 使用 Access Token 访问受保护资源安全建议
- 始终使用 PKCE: 即使是机密客户端也建议使用 PKCE。
- 验证 state 参数: 防止 CSRF 攻击。
- 验证 ID Token: 验证签名、iss、aud、exp 等声明。
- 安全存储令牌: 不要将令牌存储在 localStorage 中,推荐使用 HttpOnly Cookie 或内存存储。
- 及时刷新令牌: 在 Access Token 过期前使用 Refresh Token 获取新令牌。
- 登出时撤销令牌: 调用
/oidc/revoke撤销 Refresh Token。
