---
title: OIDC 高级接入与能力参考
description: 使用签名请求、签名 UserInfo、Form Post 和原生 OIDC 回调。
editUrl: true
head: []
template: doc
sidebar:
  order: 5
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

先按 [Code + PKCE](/zh-cn/oidc/flow/) 接入。RP 需要其他响应类型、签名消息或原生回调时，再使用本页。配置库前，检查部署环境的 discovery。

## 响应类型和 grant

| `response_type` | 授权响应 | 所需 grant | 前通道 ID Token 哈希 | 刷新能力 |
|---|---|---|---|---|
| `code` | 授权码 | `authorization_code` | 无前通道 ID Token | 允许 `refresh_token` 且用户同意 `offline_access` 时可用 |
| `id_token` | 含获准资料声明的 ID Token | `implicit` | 两者都没有 | 无 |
| `id_token token` | ID Token 和 access token | `implicit` | `at_hash` | 无 |
| `code id_token` | 授权码和 ID Token | `authorization_code`、`implicit` | `c_hash` | 允许 `refresh_token` 且用户同意 `offline_access` 时可用 |
| `code token` | 授权码和 access token | `authorization_code`、`implicit` | 无前通道 ID Token | 无 |
| `code id_token token` | 授权码、ID Token 和 access token | `authorization_code`、`implicit` | `c_hash`、`at_hash` | 无 |

每种所选类型都须显式启用。`code` 默认使用 `query`；Implicit 和 Hybrid 默认使用 `fragment`。六种类型都支持 `form_post`。Implicit 和 Hybrid 不接受 `query`。前通道返回 ID Token 时，请求必须提供非空 `nonce`。

含授权码的流程中，公开客户端必须使用 PKCE S256，机密客户端建议使用。纯 Implicit 忽略 PKCE。纯 Implicit 和携带 access token 的 Hybrid 在同意流程前移除 `offline_access`。Token 端点返回的 ID Token 包含对应 access token 的 `at_hash`，不含 `c_hash`。

## 授权参数

| 参数 | 用途 |
|---|---|
| `client_id`、`response_type`、`scope` | 指定客户端和流程。scope 必须包含 `openid`。 |
| `redirect_uri` | 精确匹配已注册回调。 |
| `state` | 将回调关联到本次授权。 |
| `nonce` | 将 ID Token 关联到请求；前通道 ID Token 必须使用。 |
| `code_challenge`、`code_challenge_method` | 含授权码流程的 PKCE 关联，只支持 S256。 |
| `response_mode` | 按流程限制选择 `query`、`fragment` 或 `form_post`。 |
| `prompt`、`max_age` | 控制交互和认证时间要求。见[交互行为](/zh-cn/oidc/flow/#选择登录交互方式)。 |
| `acr_values` | 以空格分隔的认证上下文偏好。允许敏感操作前，检查返回的 `acr`。 |
| `id_token_hint` | 提供属于此客户端的有效 ID Token。它本身不能建立可复用登录。 |
| `ui_locales` | 偏好的界面语言，支持 `en-US` 和 `zh-CN`。 |
| `request`、`request_uri` | 选择一种签名 Request Object 传递方式。 |

请求没有提供对应参数时，使用 `default_max_age` 和 `default_acr_values`。`require_auth_time` 要求 ID Token 包含 `auth_time`。托管规则使用对应的 camelCase 字段，见[返回规则](/zh-cn/application-rules/return-rules/)。

## 签名 Request Object

动态注册配置 `request_object_signing_alg="RS256"`，托管 OIDC 规则配置 `requestObjectSigningAlg="RS256"`。配置所选 [RP 密钥来源](/zh-cn/oidc/relying-party-keys/)。下例使用该指南生成的私钥。

保存为 `signed-request.mjs`。将 `CLIENT_ID` 和 `REDIRECT_URI` 设置为已注册的值。`ISSUER` 默认使用生产 issuer。

```js
import { createHash, randomBytes, sign } from 'node:crypto';
import { readFileSync, writeFileSync } from 'node:fs';

const issuer = process.env.ISSUER ?? 'https://oidc.sudomimus.com';
const clientId = process.env.CLIENT_ID;
const redirectUri = process.env.REDIRECT_URI;
if (!clientId || !redirectUri) throw new Error('Set CLIENT_ID and REDIRECT_URI');
const now = Math.floor(Date.now() / 1000);
const verifier = randomBytes(32).toString('base64url');
const state = randomBytes(32).toString('base64url');
const nonce = randomBytes(32).toString('base64url');
const encode = value => Buffer.from(JSON.stringify(value)).toString('base64url');
const header = { alg: 'RS256', kid: 'rp-signing-1', typ: 'JWT' };
const payload = {
  iss: clientId,
  aud: issuer,
  iat: now,
  exp: now + 120,
  client_id: clientId,
  response_type: 'code',
  scope: 'openid email profile',
  redirect_uri: redirectUri,
  state,
  nonce,
  code_challenge: createHash('sha256').update(verifier).digest('base64url'),
  code_challenge_method: 'S256',
};
const input = `${encode(header)}.${encode(payload)}`;
const signature = sign('RSA-SHA256', Buffer.from(input),
  readFileSync('rp-private.pem')).toString('base64url');
const request = `${input}.${signature}`;
writeFileSync('pending-authorization.json', JSON.stringify({
  issuer, clientId, redirectUri, state, nonce, verifier,
}), { mode: 0o600, flag: 'wx' });
writeFileSync('request.jwt', request, { mode: 0o600, flag: 'wx' });
const url = new URL(`${issuer}/authorize`);
url.search = new URLSearchParams({
  client_id: clientId,
  response_type: 'code',
  scope: 'openid email profile',
  request,
}).toString();
console.log(url.toString());
```

将用户浏览器跳转到输出的 URL。待完成的请求信息保留在后端。回调时验证 `state` 和 `iss`，用保存的 verifier 兑换授权码，再验证 ID Token 的 nonce。示例文件只表示一次请求；实际 RP 需要能隔离并发请求、清理到期数据的存储。

使用 `request_uri` 时，通过公开 HTTPS URL 提供 `request.jwt` 的紧凑 JWT 内容。外层发送 `client_id`、`response_type`、包含 `openid` 的 `scope` 和 `request_uri`，不再发送 `request`。已注册的 `request_uris` 列表非空时，会限制可用引用；URI fragment 不改变获取的引用。

签名对象中的值覆盖其他外层参数。内层提供 `client_id` 和 `response_type` 时，必须与外层一致。提供 `iss` 时必须指向客户端，`aud` 必须包含 issuer。时间声明也会被验证。不接受未签名、加密或嵌套的 Request Object。不要将 token 端点的客户端 assertion 用作 Request Object，它们的 audience 和内容不同。

## 签名 UserInfo

动态注册配置 `userinfo_signed_response_alg="RS256"`，托管规则配置 `userinfoSignedResponseAlg="RS256"`。然后调用 discovery 中的 `userinfo_endpoint`：

```bash
curl "$USERINFO_ENDPOINT" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

响应是 `application/jwt`，不是 JSON。使用提供方 discovery 中的 `jwks_uri` 验证 RS256，按精确 `kid` 选择密钥，并验证 `iss`、`aud` 和到期时间。使用资料声明前，将 `sub` 与 ID Token 的 subject 比较。不要使用应用 access-token JWKS 验证签名 UserInfo。

签名响应按签发时的 scope、策略和用户同意返回资料。它是快照，撤销同意不能收回已交付的 JWT。`Accept` header 不会覆盖客户端注册的响应格式。

## Form Post 回调

在普通授权请求中加入 `response_mode=form_post`。例如：

```text
https://oidc.sudomimus.com/authorize
  ?client_id=example-rp
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Foidc%2Fcallback
  &response_type=code
  &response_mode=form_post
  &scope=openid
  &state=<transaction state>
  &nonce=<transaction nonce>
  &code_challenge=<S256 challenge>
  &code_challenge_method=S256
```

HTTP(S) 回调收到的表单 POST 类似：

```http
POST /oidc/callback HTTP/1.1
Host: app.example.com
Content-Type: application/x-www-form-urlencoded

code=<authorization-code>&state=<transaction-state>&iss=https%3A%2F%2Foidc.sudomimus.com
```

解析表单，再验证已保存请求的 `state` 和 `iss`。使用原始回调 URI 和 verifier 兑换授权码。Implicit 或 Hybrid 返回令牌时，先验证 ID Token、nonce 和适用哈希，再接受令牌。

如果用 Cookie 查找待完成的请求，Cookie 必须通过 `SameSite=None; Secure` 支持跨站 POST。不要因此跳过请求关联验证。原生自定义 scheme 不能接收 Form Post。

## 原生 OIDC 回调

为托管原生客户端配置以下规则：

```json
{
  "returnMethod": "OIDC",
  "payload": {
    "applicationType": "native",
    "redirectUris": ["com.example.app:/oidc/callback", "http://127.0.0.1:49152/oidc/callback"],
    "postLogoutRedirectUris": [],
    "allowedResponseTypes": ["code"],
    "allowedGrantTypes": ["authorization_code"],
    "allowedScopes": ["openid", "email", "profile"],
    "tokenEndpointAuthMethod": "none"
  }
}
```

通过系统浏览器启动 Code + PKCE。用已注册的自定义 scheme 或回环监听器接收响应。验证 `state` 和 `iss` 后，使用同一个精确回调 URI 和 verifier 兑换授权码。不要在安装到用户设备的应用中嵌入客户端密钥或平台私钥。

原生 HTTP 回调必须使用 localhost 或回环地址。`application_type=native` 不接受 HTTPS 回调。精确匹配包含端口；示例不允许任意回环端口。动态注册没有主机的自定义 scheme 时，还需要 sector 文档确定获准的归属。见[注册元数据](/zh-cn/oidc/dynamic-registration/#元数据规则)。

## 能力限制

提供方支持成对 subject、RS256 签名和上表的响应类型。不支持 OAuth-only `response_type=token`、匿名动态注册、通过 RAT 更新元数据，或加密的 ID Token、UserInfo 和 Request Object。

第三方发起登录使用 RP 已注册的 `initiate_login_uri`。RP 接受 GET 和 POST，验证 `iss`，再启动普通授权请求。Issuer-host WebFinger 位于 `/.well-known/webfinger`，用于发现 issuer，不应据此假定邮箱域名映射。

## 相关文档

- [OIDC 流程](/zh-cn/oidc/flow/)
- [动态注册](/zh-cn/oidc/dynamic-registration/)
- [RP 密钥](/zh-cn/oidc/relying-party-keys/)
- [排错](/zh-cn/oidc/troubleshooting/)