@eduwork/dsh-oidc 0.2.4 → 0.3.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.en.md +6 -0
  2. package/CHANGELOG.md +6 -0
  3. package/README.md +20 -132
  4. package/README_EN.md +20 -177
  5. package/docs/EDUWORK-MIGRATION.en.md +3 -25
  6. package/docs/EDUWORK-MIGRATION.md +3 -25
  7. package/docs/account-extensions.en.md +6 -6
  8. package/docs/account-extensions.md +3 -3
  9. package/docs/architecture.en.md +12 -117
  10. package/docs/architecture.md +12 -118
  11. package/docs/compatibility.en.md +6 -40
  12. package/docs/compatibility.md +6 -40
  13. package/docs/decisions/0001-one-plugin-composition-boundary.en.md +2 -2
  14. package/docs/decisions/0001-one-plugin-composition-boundary.md +2 -2
  15. package/docs/desktop-host.en.md +4 -5
  16. package/docs/desktop-host.md +8 -9
  17. package/docs/development.en.md +2 -2
  18. package/docs/development.md +2 -2
  19. package/docs/dsh-integration.en.md +11 -46
  20. package/docs/dsh-integration.md +12 -47
  21. package/docs/ecnu-reference.en.md +4 -61
  22. package/docs/ecnu-reference.md +4 -61
  23. package/docs/enterprise-profile.en.md +11 -32
  24. package/docs/enterprise-profile.md +12 -33
  25. package/docs/gateway-auth/README.md +168 -0
  26. package/docs/gateway-auth/README_EN.md +70 -0
  27. package/docs/gateway-auth/experimental-oidc-llm.en.md +53 -0
  28. package/docs/gateway-auth/experimental-oidc-llm.md +53 -0
  29. package/docs/gateway-auth/litellm-setup.en.md +114 -0
  30. package/docs/gateway-auth/litellm-setup.md +114 -0
  31. package/docs/gateway-auth/oidc-llm-draft.en.md +141 -0
  32. package/docs/gateway-auth/oidc-llm-draft.md +141 -0
  33. package/docs/getting-started.en.md +17 -122
  34. package/docs/getting-started.md +16 -225
  35. package/docs/key-binding-protocol.en.md +10 -220
  36. package/docs/key-binding-protocol.md +10 -220
  37. package/docs/oidc-interoperability.en.md +1 -1
  38. package/docs/oidc-interoperability.md +1 -1
  39. package/docs/open-integration.en.md +5 -67
  40. package/docs/open-integration.md +5 -67
  41. package/docs/public-resource-protocol.en.md +8 -44
  42. package/docs/public-resource-protocol.md +8 -43
  43. package/docs/security-model.en.md +10 -132
  44. package/docs/security-model.md +10 -132
  45. package/docs/server-integration-contract.en.md +12 -813
  46. package/docs/server-integration-contract.md +12 -825
  47. package/examples/ecnu.enterprise-profile.example.json +16 -20
  48. package/examples/enterprise-profile.example.json +16 -20
  49. package/examples/identity-only.example.json +7 -2
  50. package/examples/litellm.enterprise-profile.example.json +10 -0
  51. package/examples/oidc-llm.enterprise-profile.example.json +13 -0
  52. package/examples/resources-discovery.example.json +7 -9
  53. package/lib/callback-page.js +5 -5
  54. package/lib/client.js +31 -48
  55. package/lib/desktop-oidc.js +7 -11
  56. package/lib/gateway-backend.js +443 -0
  57. package/lib/gateway-protocol.js +6 -0
  58. package/lib/index.js +14 -15
  59. package/lib/litellm-protocol.js +117 -0
  60. package/lib/model-resource-transport.js +19 -0
  61. package/lib/oidc-llm-protocol.js +87 -0
  62. package/lib/oidc.js +40 -338
  63. package/lib/profile.js +42 -74
  64. package/lib/provider/authorization-scope.js +46 -0
  65. package/lib/provider/core.js +5 -16
  66. package/lib/provider/index.js +11 -2
  67. package/lib/transport.js +5 -0
  68. package/lib/typert-schemas.js +1 -1
  69. package/package.json +2 -2
  70. package/protocol/openapi.yaml +33 -240
  71. package/protocol/resources.openapi.yaml +8 -9
  72. package/schema/enterprise-profile.v1alpha1.schema.json +508 -107
@@ -1,836 +1,23 @@
1
- # EduWork 身份与资源接入规范(RFC EW-IDENTITY-1)
2
-
3
- > 能力范围:配额与活跃心跳属于机构专属扩展。公版不请求 /quota,resources RPC 只返回模型元数据;任何 capabilities 字符串或公版配置开关都不会启用配额。ECNU 在独立机构包中沿用原配额服务协议。
4
-
5
- > 支持两种接入模式:纯身份 OIDC 可省略全部资源接口,用户自配个人 Key;下文完整机构资源模式提供托管凭据与模型,见 [公共资源协议](public-resource-protocol.md)。下文的完整交付要求不适用于只做身份接入的场景。
1
+ # 服务端接入契约
6
2
 
7
3
  **简体中文** | [English](server-integration-contract.en.md)
8
4
 
9
- 本文面向身份平台、模型平台和联调测试人员,以接口说明的形式定义接入 `dsh-oidc` 时服务端必须提供的能力。
10
-
11
- 希望了解这套接口的目标或参与后续演进,请先看[开放身份与模型接入倡议](open-integration.md)。本规范是实现与联调依据,倡议不改变现有字段、路径或兼容性约定。
12
-
13
- 状态:现有客户端 wire 规范;不是 IETF 标准。本文以 `@eduwork/dsh-oidc` 源码及 HTTP 契约测试为准。第 10 节列出需填入本次授权结果的 curl 请求示例;第 11 节是**尚未实现的可选扩展提案**。所有客户端都是用户本机的单用户 Runtime,不设计共享 Web 服务端登录会话。
14
-
15
- | 能力档 | 最小交付 | 可选性与当前行为 |
16
- | --- | --- | --- |
17
- | 身份接入(已实现) | Discovery、Authorization、Token、JWKS、UserInfo | 不需要模型接口;用户可用个人 Key |
18
- | 托管模型(已实现) | 身份接入、Bootstrap、Key 生命周期、Chat Completions | 由 `keyBinding` 与 `provider` 一起启用;创建需用户确认 |
19
- | 模型发现(已实现) | `GET /models` | `modelSource=discovery` 时需要;静态 Profile 可省略 |
20
- | Token 刷新/撤销(已实现) | Token refresh grant / Discovery `revocation_endpoint` | Refresh Token、撤销端点可选;缺少刷新能力时到期重新登录 |
21
- | 远程机构 Profile 发现、远端模型 Key 注销(提案) | 见第 11 节 | 当前公共客户端不会因文档或 capabilities 字符串自动调用 |
22
-
23
- 实现中的“可选”表示该能力缺失不会让其他能力失效,不意味着服务端可以返回错误字段。独立接口能力应独立验收,不能将只通过 OIDC 登录描述成已完成模型接入。Profile 的机构名称、品牌、issuer、端点和模型策略来自用户或发行方的可信配置文件;Bootstrap 不能覆盖这些配置。此规范不要求任何学校专有服务。
24
-
25
- 服务端应按本文与 OpenAPI 完整输出字段和约束;客户端为兼容既有部署接受部分旧版缺省值,不代表服务端可省略安全校验。
26
-
27
- 关键词“必须”“不得”“应该”“可以”具有规范性含义。OIDC 接口遵循 OpenID Connect 与 OAuth 2.0 标准;Key Binding 是本项目定义的固定协议;模型网关使用 OpenAI Chat Completions 兼容接口。
28
-
29
- ## 1. 接入范围
30
-
31
- 一个完整的**机构企业模型集成服务**必须同时提供以下三组接口:
32
-
33
- | 接口组 | 用途 | Enterprise Profile 配置 | 要求 |
34
- | --- | --- | --- | --- |
35
- | OIDC Provider | 登录、用户身份、OIDC Access Token | `oidc.issuer`、`oidc.clientId`、`oidc.scopes` | 必须完整实现 |
36
- | Key Binding | 将已认证用户绑定到可撤销的模型运行凭据 | `keyBinding.baseURL` | 四个接口全部必须实现 |
37
- | 模型网关 | 使用绑定后的运行凭据调用企业模型 | `provider.baseURL`、`provider.id`、`provider.models` | 必须实现 Chat Completions |
38
-
39
- “一个服务”是指一套完整的产品交付、版本、安全和运维责任,不要求所有接口使用相同域名、网关或进程。机构可以在内部拆分身份平台、用户中心和模型平台,但不能只交付其中一部分并声明已经完成 `dsh-oidc` 接入。
40
-
41
- 以下组合不符合本规范:
42
-
43
- - 只实现 OIDC,然后向所有用户下发同一个全局模型 API Key;
44
- - 只实现 Key Binding,并相信客户端提交的工号、姓名或 subject;
45
- - 只提供模型地址和模型列表,不实现用户授权与凭据生命周期;
46
- - 缺少 `bootstrap`、`provision`、`resolve`、`renew` 中任意一个 Key Binding 接口。
47
-
48
- ## 2. 地址与客户端注册
49
-
50
- ### 2.1 三个服务入口
51
-
52
- Enterprise Profile 分别声明三个入口:
53
-
54
- ```json
55
- {
56
- "oidc": {
57
- "issuer": "https://id.example.edu/oidc",
58
- "clientId": "dsh-web-public-client",
59
- "scopes": ["openid", "profile", "offline_access"]
60
- },
61
- "keyBinding": {
62
- "baseURL": "https://ai.example.edu/api/worker/v1"
63
- },
64
- "provider": {
65
- "id": "example-ai",
66
- "baseURL": "https://ai.example.edu/open/api/v1",
67
- "adapter": "openai-compatible",
68
- "models": [
69
- { "id": "example-model", "input": ["text"] }
70
- ]
71
- }
72
- }
73
- ```
74
-
75
- 生产环境中的 `issuer`、`keyBinding.baseURL` 和 `provider.baseURL` 必须使用 HTTPS。URL 不得包含账号密码或 fragment;基础 URL 不得依靠 query 参数改变路由行为。
76
-
77
- ### 2.2 OIDC Public Client
78
-
79
- 身份平台必须注册一个不带 Client Secret 的 Public Client,并启用:
80
-
81
- - Authorization Code Flow;
82
- - PKCE `S256`;
83
- - `openid`、`profile` scope;
84
- - 如需 Refresh Token,再启用 `offline_access` 及相应授权策略。
85
-
86
- 桌面版默认使用随机可用端口,回调地址形式为:
87
-
88
- ```text
89
- http://127.0.0.1:<本次实际端口>/oauth/callback
90
- ```
91
-
92
- 约束如下:
93
-
94
- - host 固定为 `127.0.0.1`,不得改成 `localhost`、局域网 IP 或公网域名;
95
- - path 固定为 `/oauth/callback`;
96
- - 原生桌面 Public Client 应按 RFC 8252 接受 loopback 动态端口;本客户端监听 IPv4 `127.0.0.1`;
97
- - 只支持精确 URI 注册的 IdP 可由发行方固定 `desktop.callbackPort`,端口冲突会明确失败;下文 `3080` 仅作为示例;
98
- - 本机 Web 验证组合使用其 WebServer 端口,仍需登记对应 URI;
99
- - 这是浏览器返回用户本机 DSH 的地址,不是机构服务端地址;
100
- - 当前实现是本机单用户 Web 组合,不支持把回调部署成共享公网 Web 会话。
101
-
102
- ## 3. 接口总表
103
-
104
- | 编号 | 方法 | 地址 | 认证 | 是否必须 |
105
- | --- | --- | --- | --- | --- |
106
- | OIDC-01 | `GET` | `{issuer}/.well-known/openid-configuration` | 无 | 是 |
107
- | OIDC-02 | `GET` | Discovery 的 `authorization_endpoint` | 用户登录会话 | 是 |
108
- | OIDC-03 | `POST` | Discovery 的 `token_endpoint` | Authorization Code + PKCE | 是 |
109
- | OIDC-04 | `GET` | Discovery 的 `jwks_uri` | 无 | 是 |
110
- | OIDC-05 | `GET` | Discovery 的 `userinfo_endpoint` | OIDC Bearer Access Token | 是 |
111
- | OIDC-06 | `POST` | Discovery 的 `revocation_endpoint` | OIDC Token | 建议 |
112
- | KEY-01 | `GET` | `{keyBinding.baseURL}/bootstrap` | OIDC Bearer Access Token | 是 |
113
- | KEY-02 | `POST` | `{keyBinding.baseURL}/runtime-credential/provision` | OIDC Bearer Access Token | 是 |
114
- | KEY-03 | `POST` | `{keyBinding.baseURL}/runtime-credential/resolve` | OIDC Bearer Access Token | 是 |
115
- | KEY-04 | `POST` | `{keyBinding.baseURL}/runtime-credential/renew` | OIDC Bearer Access Token | 是 |
116
- | MODEL-01 | `POST` | `{provider.baseURL}/chat/completions` | 运行时 API Key | 是 |
117
- | MODEL-02 | `GET` | `{provider.baseURL}/models` | 运行时 API Key | 自动模型目录时必须 |
118
-
119
- 以上路径均是远端服务路径。`/oauth/callback` 是客户端本机地址,`oidcAccounts.*` 是本机 Host RPC,不应作为服务端 REST API 实现。
120
-
121
- ## 4. OIDC 接口组
122
-
123
- ### OIDC-01:Discovery
124
-
125
- 读取 OIDC Provider 元数据。
126
-
127
- #### 请求
128
-
129
- ```http
130
- GET /oidc/.well-known/openid-configuration HTTP/1.1
131
- Host: id.example.edu
132
- Accept: application/json
133
- ```
134
-
135
- 当 `issuer` 本身带路径时,Discovery 地址为:
136
-
137
- ```text
138
- {issuer去除末尾斜杠}/.well-known/openid-configuration
139
- ```
140
-
141
- #### 成功响应
142
-
143
- ```http
144
- HTTP/1.1 200 OK
145
- Content-Type: application/json
146
-
147
- {
148
- "issuer": "https://id.example.edu/oidc",
149
- "authorization_endpoint": "https://id.example.edu/oidc/authorize",
150
- "token_endpoint": "https://id.example.edu/oidc/token",
151
- "jwks_uri": "https://id.example.edu/oidc/jwks",
152
- "userinfo_endpoint": "https://id.example.edu/oidc/userinfo",
153
- "revocation_endpoint": "https://id.example.edu/oidc/revoke",
154
- "code_challenge_methods_supported": ["S256"],
155
- "id_token_signing_alg_values_supported": ["RS256"]
156
- }
157
- ```
158
-
159
- #### 字段要求
160
-
161
- | 字段 | 必需 | 要求 |
162
- | --- | --- | --- |
163
- | `issuer` | 是 | 必须与 Enterprise Profile 的 `oidc.issuer` 逐字符相同 |
164
- | `authorization_endpoint` | 是 | 绝对 HTTPS URL |
165
- | `token_endpoint` | 是 | 绝对 HTTPS URL |
166
- | `jwks_uri` | 是 | 绝对 HTTPS URL |
167
- | `userinfo_endpoint` | 是 | 本互操作规范强制要求 |
168
- | `revocation_endpoint` | 否 | 提供后,客户端退出时会尽力撤销 Token |
169
- | `code_challenge_methods_supported` | 是 | 必须包含 `S256` |
170
- | `id_token_signing_alg_values_supported` | 应该 | 应包含 `RS256`;实际 ID Token 必须使用 RS256 |
171
-
172
- Discovery 声明的 endpoint 可以位于不同的 HTTPS origin。开发环境 HTTP 只允许 Profile 明确声明的单一开发 origin,或 loopback origin。
173
-
174
- ### OIDC-02:Authorization
175
-
176
- 浏览器通过 Authorization Code + PKCE 发起登录。
177
-
178
- #### 请求
179
-
180
- ```http
181
- GET /oidc/authorize?
182
- response_type=code&
183
- client_id=dsh-web-public-client&
184
- redirect_uri=http%3A%2F%2F127.0.0.1%3A3080%2Foauth%2Fcallback&
185
- scope=openid%20profile%20offline_access&
186
- state=<random-state>&
187
- nonce=<random-nonce>&
188
- code_challenge=<base64url-sha256>&
189
- code_challenge_method=S256 HTTP/1.1
190
- Host: id.example.edu
191
- ```
192
-
193
- #### 查询参数
194
-
195
- | 参数 | 要求 |
196
- | --- | --- |
197
- | `response_type` | 固定为 `code` |
198
- | `client_id` | 等于 Profile 的 `oidc.clientId` |
199
- | `redirect_uri` | 与注册的 loopback 回调精确一致 |
200
- | `scope` | 至少包含 `openid profile` |
201
- | `state` | 客户端生成,服务端必须原样返回 |
202
- | `nonce` | 客户端生成,必须进入对应 ID Token |
203
- | `code_challenge` | PKCE challenge |
204
- | `code_challenge_method` | 固定为 `S256` |
205
-
206
- #### 成功回调
207
-
208
- ```http
209
- HTTP/1.1 302 Found
210
- Location: http://127.0.0.1:3080/oauth/callback?code=<authorization-code>&state=<original-state>
211
- ```
212
-
213
- 如果支持 RFC 9207,可以额外返回 `iss`;其值必须等于 Profile 中的 issuer。
214
-
215
- #### 失败回调
216
-
217
- ```text
218
- http://127.0.0.1:3080/oauth/callback?error=access_denied&error_description=...&state=<original-state>
219
- ```
220
-
221
- 服务端不得省略 `state`。Authorization Code 必须一次性使用、短期有效,并绑定到 client、redirect URI 和 PKCE challenge。
222
-
223
- ### OIDC-03:Token
224
-
225
- 客户端使用 Authorization Code 和 PKCE verifier 换取 Token。Public Client 不发送 Client Secret。
226
-
227
- #### Authorization Code 请求
228
-
229
- ```http
230
- POST /oidc/token HTTP/1.1
231
- Host: id.example.edu
232
- Content-Type: application/x-www-form-urlencoded
233
- Accept: application/json
234
-
235
- grant_type=authorization_code&
236
- client_id=dsh-web-public-client&
237
- code=<authorization-code>&
238
- redirect_uri=http%3A%2F%2F127.0.0.1%3A3080%2Foauth%2Fcallback&
239
- code_verifier=<pkce-verifier>
240
- ```
241
-
242
- #### 成功响应
243
-
244
- ```http
245
- HTTP/1.1 200 OK
246
- Content-Type: application/json
247
- Cache-Control: no-store
248
-
249
- {
250
- "access_token": "<oidc-access-token>",
251
- "token_type": "Bearer",
252
- "expires_in": 3600,
253
- "id_token": "<signed-id-token>",
254
- "refresh_token": "<optional-refresh-token>",
255
- "scope": "openid profile offline_access"
256
- }
257
- ```
258
-
259
- | 字段 | 必需 | 要求 |
260
- | --- | --- | --- |
261
- | `access_token` | 是 | 非空;供 UserInfo 和 Key Binding 使用 |
262
- | `token_type` | 是 | 必须为 `Bearer`,大小写不敏感 |
263
- | `expires_in` | 应该 | 正数秒;缺失时客户端按 3600 秒处理 |
264
- | `id_token` | 是 | RS256 签名 JWT |
265
- | `refresh_token` | 否 | 启用 `offline_access` 时建议返回 |
266
- | `scope` | 否 | 实际授权 scope |
267
-
268
- ID Token 至少必须包含:
269
-
270
- | Claim | 要求 |
271
- | --- | --- |
272
- | `iss` | 等于 Profile 的 `oidc.issuer` |
273
- | `aud` | 包含 `oidc.clientId`;多 audience 时 `azp` 必须等于该 client ID |
274
- | `sub` | 非空、稳定的用户标识 |
275
- | `iat`、`exp` | 有效 NumericDate |
276
- | `nonce` | 与 Authorization 请求完全一致 |
277
- | `nbf` | 可选;提供时必须有效 |
278
- | `at_hash` | 可选;提供时必须与 Access Token 匹配 |
279
-
280
- JWT header 必须使用 `alg=RS256`,并提供非空 `kid`。
281
-
282
- #### Refresh Token 请求
283
-
284
- ```http
285
- POST /oidc/token HTTP/1.1
286
- Content-Type: application/x-www-form-urlencoded
287
- Accept: application/json
288
-
289
- grant_type=refresh_token&
290
- client_id=dsh-web-public-client&
291
- refresh_token=<refresh-token>
292
- ```
293
-
294
- 响应必须至少包含新的 `access_token`、`token_type=Bearer` 和有效 `expires_in`。如果轮换 Refresh Token,应同时返回新的 `refresh_token`。`invalid_grant` 会导致客户端清除本地 OIDC 会话和模型运行凭据。
295
-
296
- #### 错误响应
297
-
298
- Token 错误遵循 OAuth 2.0:
299
-
300
- ```http
301
- HTTP/1.1 400 Bad Request
302
- Content-Type: application/json
303
- Cache-Control: no-store
304
-
305
- {
306
- "error": "invalid_grant",
307
- "error_description": "Authorization code is invalid or expired"
308
- }
309
- ```
310
-
311
- ### OIDC-04:JWKS
312
-
313
- 提供验证 ID Token 所需的公开签名密钥。
314
-
315
- #### 请求
316
-
317
- ```http
318
- GET /oidc/jwks HTTP/1.1
319
- Accept: application/json
320
- ```
321
-
322
- #### 成功响应
323
-
324
- ```json
325
- {
326
- "keys": [
327
- {
328
- "kty": "RSA",
329
- "kid": "signing-key-2026-01",
330
- "use": "sig",
331
- "alg": "RS256",
332
- "n": "<base64url-modulus>",
333
- "e": "AQAB"
334
- }
335
- ]
336
- }
337
- ```
338
-
339
- 用于签发 ID Token 的 `kid` 必须唯一匹配一个 RSA 签名密钥。密钥轮换时应该保留旧公钥,直到所有使用旧密钥签发的有效 Token 均已过期。
340
-
341
- ### OIDC-05:UserInfo
342
-
343
- 返回当前 Access Token 对应的标准用户身份。
344
-
345
- #### 请求
346
-
347
- ```http
348
- GET /oidc/userinfo HTTP/1.1
349
- Authorization: Bearer <oidc-access-token>
350
- Accept: application/json
351
- ```
352
-
353
- #### 成功响应
354
-
355
- ```http
356
- HTTP/1.1 200 OK
357
- Content-Type: application/json
358
-
359
- {
360
- "sub": "user-12345",
361
- "name": "示例用户"
362
- }
363
- ```
364
-
365
- | 字段 | 必需 | 用途 |
366
- | --- | --- | --- |
367
- | `sub` | 是 | 必须与 ID Token `sub` 完全一致 |
368
- | `name` | 应该 | 界面显示姓名;缺失或为空时客户端显示 `sub` |
369
-
370
- 服务端应该直接提供标准 `name`。`dsh-oidc` 不调用机构私有人员接口,也不支持通过 JSON Path 映射姓名。其他标准 Claim 可以返回,但不得代替 `sub` 参与绑定。
371
-
372
- ### OIDC-06:Revocation(建议)
373
-
374
- 如果 Discovery 发布 `revocation_endpoint`,客户端退出时会提交 Refresh Token;没有 Refresh Token 时提交 Access Token。
375
-
376
- ```http
377
- POST /oidc/revoke HTTP/1.1
378
- Content-Type: application/x-www-form-urlencoded
379
-
380
- token=<refresh-or-access-token>&client_id=dsh-web-public-client
381
- ```
382
-
383
- 退出操作对撤销采用尽力而为语义。无论远端撤销是否成功,本地 OIDC 会话和模型运行凭据都会清除。模型运行 API Key 的远程撤销仍由 Key Binding 与模型平台的生命周期策略负责。
384
-
385
- ## 5. Key Binding 接口组
386
-
387
- ### 5.1 通用约定
388
-
389
- 四个接口的相对路径、HTTP 方法和字段名称固定,不能在 Profile 中配置。
390
-
391
- 所有请求必须携带:
392
-
393
- ```http
394
- Authorization: Bearer <oidc-access-token>
395
- Accept: application/json
396
- ```
397
-
398
- 服务端必须验证 Token 的 issuer、audience、有效期、not-before、scope、subject、账号状态和机构授权。subject 必须从验证后的 Token 派生;不得接受客户端在 body 中提交 subject、工号或姓名作为可信身份。
399
-
400
- 建议 scope:
401
-
402
- | 接口 | Scope |
403
- | --- | --- |
404
- | KEY-01 Bootstrap | `worker.bootstrap.read` |
405
- | KEY-02 Provision | `worker.credential.provision` |
406
- | KEY-03 Resolve | `worker.credential.read` |
407
- | KEY-04 Renew | `worker.credential.renew` |
408
-
409
- 机构可以使用其他 scope 名称,但必须将实际名称加入 Profile 的 `oidc.scopes`,并在服务端执行对应授权。
410
-
411
- ### KEY-01:Bootstrap
412
-
413
- 读取 Provider、服务端能力、用户授权状态和运行凭据状态。该接口不得返回模型 API Key。
414
-
415
- #### 请求
416
-
417
- ```http
418
- GET /api/worker/v1/bootstrap HTTP/1.1
419
- Host: ai.example.edu
420
- Authorization: Bearer <oidc-access-token>
421
- Accept: application/json
422
- ```
423
-
424
- #### 成功响应
425
-
426
- ```http
427
- HTTP/1.1 200 OK
428
- Content-Type: application/json
429
- Cache-Control: no-store
430
-
431
- {
432
- "protocol_version": "worker-user-center/v1",
433
- "provider": {
434
- "id": "example-ai"
435
- },
436
- "capabilities": [
437
- "worker.bootstrap.read",
438
- "worker.credential.provision",
439
- "worker.credential.read",
440
- "worker.credential.renew"
441
- ],
442
- "runtime_credential": {
443
- "status": "missing",
444
- "provisioning": {
445
- "allowed": true
446
- }
447
- }
448
- }
449
- ```
450
-
451
- #### 响应字段
452
-
453
- | 字段 | 必需 | 说明 |
454
- | --- | --- | --- |
455
- | `protocol_version` | 否(新服务应提供) | 接受 `worker.user-center.v1`、`worker-user-center/v1`、`eduwork-resources/v1`;三个名称采用本文相同 wire;未知版本拒绝 |
456
- | `provider.id` | 是 | 必须与 Profile `provider.id` 完全一致 |
457
- | `capabilities` | 是 | 字符串数组,最多 256 项 |
458
- | `runtime_credential.status` | 是 | 当前凭据状态 |
459
- | `runtime_credential.api_key_id` | 条件 | 已存在凭据时建议返回;Renew 需要该值 |
460
- | `runtime_credential.expires_at` | 否 | RFC 3339 时间 |
461
- | `runtime_credential.provisioning.allowed` | 否 | 是否允许创建凭据 |
462
- | `runtime_credential.provisioning.reason` | 否 | 面向用户的非敏感原因,最多 500 字符 |
463
- | `subject` | 否 | 仅供管理面展示;不得覆盖 OIDC 身份 |
464
-
465
- `runtime_credential.status` 的取值:
466
-
467
- | 状态 | 客户端行为 |
468
- | --- | --- |
469
- | `missing` | 等待用户明确同意后调用 Provision |
470
- | `active` | 调用 Resolve |
471
- | `expiring` | 有 `api_key_id` 时调用 Renew,否则调用 Resolve |
472
- | `expired` | 有 `api_key_id` 时调用 Renew,否则调用 Resolve |
473
- | `suspended` | 清除本地运行凭据,不继续调用模型 |
474
- | `unavailable` | 清除本地运行凭据,显示服务不可用 |
475
-
476
- ### KEY-02:Provision
477
-
478
- 为已认证且已获授权的 subject 创建或绑定运行凭据。仅完成 OIDC 回调不会触发该接口;用户必须在界面中明确同意。
479
-
480
- #### 请求
481
-
482
- ```http
483
- POST /api/worker/v1/runtime-credential/provision HTTP/1.1
484
- Authorization: Bearer <oidc-access-token>
485
- Content-Type: application/json
486
- Accept: application/json
487
- Idempotency-Key: 2b9b6c80-7758-4d3f-94c6-b72d91ca8c53
488
-
489
- {
490
- "provider_id": "example-ai"
491
- }
492
- ```
493
-
494
- | 字段/Header | 必需 | 要求 |
495
- | --- | --- | --- |
496
- | `Idempotency-Key` | 是 | 一个逻辑创建操作对应一个 UUID |
497
- | `provider_id` | 是 | 必须与 Profile `provider.id` 一致 |
498
-
499
- 服务端必须以“已认证 subject + Provider + Idempotency-Key”为幂等范围。相同 key 和相同 payload 的重试必须返回相同逻辑结果;相同 key 携带不同 payload 必须返回 `409 Conflict`。
500
-
501
- #### 成功响应
502
-
503
- 返回格式见[运行凭据统一响应](#55-运行凭据统一响应)。首次创建可以返回 `201 Created`,已经存在或完成绑定时可以返回 `200 OK`。
504
-
505
- ### KEY-03:Resolve
506
-
507
- 解析已经绑定到当前 subject 的活动运行凭据。Resolve 不得创建新的授权资格。
508
-
509
- #### 请求
510
-
511
- ```http
512
- POST /api/worker/v1/runtime-credential/resolve HTTP/1.1
513
- Authorization: Bearer <oidc-access-token>
514
- Content-Type: application/json
515
- Accept: application/json
516
-
517
- {
518
- "provider_id": "example-ai",
519
- "api_key_id": "key-123"
520
- }
521
- ```
522
-
523
- | 字段 | 必需 | 说明 |
524
- | --- | --- | --- |
525
- | `provider_id` | 是 | Profile 中声明的 Provider ID |
526
- | `api_key_id` | 否 | Bootstrap 返回的已知凭据 ID |
527
-
528
- 不存在可解析凭据时返回 `404`。服务端可以轮换不透明秘密值,但不得把其他 subject 或 Provider 的凭据返回给调用方。
529
-
530
- ### KEY-04:Renew
531
-
532
- 轮换即将过期或已经过期的运行凭据。
533
-
534
- #### 请求
535
-
536
- ```http
537
- POST /api/worker/v1/runtime-credential/renew HTTP/1.1
538
- Authorization: Bearer <oidc-access-token>
539
- Content-Type: application/json
540
- Accept: application/json
541
- Idempotency-Key: 2f94dc48-201a-475e-a61a-69d7880e1186
542
-
543
- {
544
- "provider_id": "example-ai",
545
- "api_key_id": "key-123"
546
- }
547
- ```
548
-
549
- `provider_id`、`api_key_id` 和 `Idempotency-Key` 均为必需。幂等规则与 Provision 相同。新凭据生效后,服务端应该尽快使旧秘密失效,并记录任何必要的重叠有效窗口。
550
-
551
- ### 5.5 运行凭据统一响应
552
-
553
- Provision、Resolve 和 Renew 成功时使用相同响应结构:
554
-
555
- ```http
556
- HTTP/1.1 200 OK
557
- Content-Type: application/json
558
- Cache-Control: no-store
559
- Pragma: no-cache
560
-
561
- {
562
- "provider_id": "example-ai",
563
- "api_key": "<secret-runtime-api-key>",
564
- "api_key_id": "key-123",
565
- "status": "active",
566
- "expires_at": "2026-09-01T00:00:00Z"
567
- }
568
- ```
569
-
570
- | 字段 | 必需 | 要求 |
571
- | --- | --- | --- |
572
- | `provider_id` | 是 | 必须与请求及 Profile 完全一致 |
573
- | `api_key` | 是 | 非空,最多 16 KiB;只用于模型网关 |
574
- | `api_key_id` | 否 | 建议返回,用于生命周期管理和审计 |
575
- | `status` | 是 | 固定为 `active` |
576
- | `expires_at` | 否 | RFC 3339 时间 |
577
-
578
- 包含 `api_key` 的响应必须使用 `Cache-Control: no-store`,并且应该使用 `Pragma: no-cache`。反向代理、WAF、APM、应用日志和审计日志都不得记录 Authorization header 或 `api_key`。
579
-
580
- ### 5.6 Key Binding 错误响应
581
-
582
- 错误应该使用 RFC 9457 `application/problem+json`:
583
-
584
- ```http
585
- HTTP/1.1 403 Forbidden
586
- Content-Type: application/problem+json
587
-
588
- {
589
- "type": "https://ai.example.edu/problems/model-entitlement-denied",
590
- "title": "Model entitlement denied",
591
- "status": 403,
592
- "detail": "The current account cannot use this provider",
593
- "code": "model_entitlement_denied"
594
- }
595
- ```
596
-
597
- | HTTP 状态 | 含义 |
598
- | --- | --- |
599
- | `400` | 请求结构或 Provider ID 无效 |
600
- | `401` | Access Token 缺失、无效、过期或 audience 错误;应该返回 `WWW-Authenticate` |
601
- | `403` | scope、机构成员资格、模型授权、账号状态或创建策略拒绝 |
602
- | `404` | 不存在可解析或可更新的凭据 |
603
- | `409` | 幂等冲突或凭据生命周期状态冲突 |
604
- | `429` | 限流;应该返回 `Retry-After` |
605
- | `5xx` | 服务端错误;响应不得包含 Token 或 API Key |
606
-
607
- `code` 应保持稳定,供界面和日志分类;`detail` 可以变化,但不得泄露敏感信息。
608
-
609
- 机器可读的 Key Binding 契约见 [`protocol/openapi.yaml`](../protocol/openapi.yaml)。
610
-
611
- ## 6. 模型网关接口组
612
-
613
- ### MODEL-01:Chat Completions
614
-
615
- `dsh-oidc` 当前通过 DSH 官方 Pi adapter 调用 OpenAI Chat Completions 兼容接口。
616
-
617
- #### 请求
618
-
619
- ```http
620
- POST /open/api/v1/chat/completions HTTP/1.1
621
- Host: ai.example.edu
622
- Authorization: Bearer <runtime-api-key>
623
- Content-Type: application/json
624
- Accept: text/event-stream
625
-
626
- {
627
- "model": "example-model",
628
- "messages": [
629
- { "role": "user", "content": "你好" }
630
- ],
631
- "stream": true,
632
- "max_tokens": 1024
633
- }
634
- ```
635
-
636
- | 项目 | 要求 |
637
- | --- | --- |
638
- | 认证 | 接受 Key Binding 返回的 `api_key`,不得接受 OIDC Token 代替 |
639
- | `model` | 必须实现 Profile `provider.models` 中声明的模型 ID |
640
- | `messages` | 支持 OpenAI Chat Completions 消息结构 |
641
- | `stream` | 必须支持流式响应 |
642
- | 图片输入 | 只有 Profile 为该模型声明 `input: ["text", "image"]` 时才要求支持 |
643
- | Reasoning 参数 | 必须与 Profile 的 `reasoning`、`reasoningEfforts` 和 `compat` 声明一致 |
644
- | Tools | 必须支持 `tools`、`tool_choice` 和流式 `tool_calls`,供 DSH Agent 调用工具 |
645
-
646
- #### 流式成功响应
647
-
648
- ```http
649
- HTTP/1.1 200 OK
650
- Content-Type: text/event-stream
651
- Cache-Control: no-cache
652
-
653
- data: {"id":"chatcmpl-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"你"}}]}
654
-
655
- data: {"id":"chatcmpl-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null}]}
656
-
657
- data: {"id":"chatcmpl-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
658
-
659
- data: [DONE]
660
- ```
661
-
662
- 服务端必须按运行 API Key 检查 subject 绑定、Provider scope、凭据状态、过期时间、撤销状态和配额策略(如部署启用);该服务端策略不要求提供公版配额 UI 或 `/quota` 接口。已经撤销或轮换的旧 Key 不得无限期继续访问。
663
-
664
- #### 错误响应
665
-
666
- 错误应与 OpenAI-compatible 客户端互操作,例如:
667
-
668
- ```json
669
- {
670
- "error": {
671
- "message": "Runtime credential has expired",
672
- "type": "authentication_error",
673
- "code": "credential_expired"
674
- }
675
- }
676
- ```
677
-
678
- 至少应正确区分:`400` 请求错误、`401` 凭据无效、`403` 无权访问、`404` 模型不存在、`429` 限流或配额不足、`5xx` 服务端错误。
679
-
680
- 模型目录可以由可信 Enterprise Profile 声明,也可以通过以下接口读取。静态 `modelSource=profile` 不要求 `/models`;自动发现不能以硬编码的虚假模型作为备用。
681
-
682
- ### MODEL-02:模型目录
683
-
684
- ```http
685
- GET /open/api/v1/models HTTP/1.1
686
- Host: ai.example.edu
687
- Authorization: Bearer <runtime-api-key>
688
- Accept: application/json
689
- ```
690
-
691
- ```http
692
- HTTP/1.1 200 OK
693
- Content-Type: application/json
694
- Cache-Control: no-store
695
-
696
- {"data":[{"id":"example-model","object":"model","owned_by":"example-ai"}]}
697
- ```
698
-
699
- `data` 是最多 128 个不同模型的数组;每项 `id` 为 1–256 字符字符串,服务器必须返回当前 Key 可用的公开模型 ID。当前客户端只消费 ID:已在 Profile 声明的模型沿用其上下文、输入模态和 reasoning 档位;新 ID 默认文本输入、不声明 reasoning_effort。OpenAI 常见 `object`、`owned_by` 字段可以返回,但不能借此下发执行代码、任意 endpoint 或未经审核的能力。空/无效目录或查询失败保留上一份有效目录,记录 `models_unavailable`。如需准确多模态/上下文/推理能力,当前版本在可信 Profile 中配置;远程能力描述是后续协议演进,不应冒充已支持。
700
-
701
- ## 7. 三组接口的一致性要求
702
-
703
- 服务端必须保证:
704
-
705
- 1. UserInfo `sub` 与 ID Token `sub` 完全一致;
706
- 2. Key Binding 从 Access Token 得到同一个 subject,不信任请求 body 中的身份;
707
- 3. Profile `provider.id`、Bootstrap `provider.id` 和运行凭据 `provider_id` 完全一致;
708
- 4. 运行 API Key 只授权给对应 subject 和 Provider;
709
- 5. 模型网关实际提供的模型 ID、输入模态和 reasoning 能力与 Profile 声明一致;
710
- 6. OIDC Token 只用于 UserInfo 和 Key Binding,运行 API Key 只用于模型网关,两者不能互换;
711
- 7. 账号停用、授权撤销和凭据轮换能够传播到运行凭据及模型网关;
712
- 8. 日志可以记录关联 ID、不透明 subject 引用、Provider ID 和 `api_key_id`,但不得记录 Token 或 `api_key`;
713
- 9. Bootstrap 中的 `subject` 只能用于辅助展示,不能覆盖 OIDC `sub` 或 `name`。
714
-
715
- ## 8. 标准调用顺序
716
-
717
- ```text
718
- 客户端 -> OIDC-01 Discovery
719
- 客户端 -> OIDC-02 Authorization(浏览器 + PKCE)
720
- OIDC -> 127.0.0.1:<port>/oauth/callback
721
- 客户端 -> OIDC-03 Token
722
- 客户端 -> OIDC-04 JWKS(校验 ID Token)
723
- 客户端 -> OIDC-05 UserInfo
724
- 客户端 -> KEY-01 Bootstrap
725
- 客户端 -> KEY-02 Provision(首次且用户明确同意)
726
- 或 KEY-03 Resolve(凭据活动)
727
- 或 KEY-04 Renew(凭据即将/已经过期)
728
- 客户端 -> MODEL-01 Chat Completions
729
- ```
730
-
731
- 客户端在 Access Token 即将过期时使用 OIDC-03 的 Refresh Token grant。退出时清除本地 OIDC 会话及仍归该会话所有的运行凭据,并在可用时调用 OIDC-06。
732
-
733
- 当前实现细节:管理请求在 Access Token 距到期不足 90 秒时尝试刷新,同一 Profile 并发刷新合并;管理面收到 401 时可刷新后重试**一次**,保留同一次请求的 body 与 Idempotency-Key。没有 Refresh Token、或刷新返回 `invalid_grant` 时清除该机构本地身份及仍归其所有的 Key,要求重新登录;429/5xx/断网不会清除有效身份。`status()` 是安全状态投影,不代表每次都会刷新 Token;“刷新账户”通过 `reconcile()` 进行有效性检查。
734
-
735
- 通用 Host 授权请求使用已批准的 origin,管理及模型资源请求不跟随重定向。单次普通 OIDC/管理/资源请求超时 20 秒,注销撤销请求 10 秒。当前客户端不会自动按 Retry-After 排队重试 Bootstrap、Key 操作或模型目录;429/503 应给出该 Header,供操作者重试和后续调度器使用。客户端只在本次请求的 401 重试中复用幂等 Key;用户重新点击会生成新 Key,服务端还必须对同 subject/provider 的 Provision 保证不会无限创建重复凭据。
736
-
737
- “退出登录”是该应用的本地注销:清除该机构 OIDC session 和托管模型 Key 快照;只有指纹仍匹配的本地 Key 才会被删除,其他会话已写入公共槽的 Key 和个人 API Key 都保留。发现撤销端点时尽力撤销 Refresh Token(没有则 Access Token),失败不阻止本地注销。它不等同于注销 IdP 浏览器 SSO、注销其他设备,也不保证远程模型 Key 已撤销;这些边界必须向服务器实现人员说明。服务器应在用户停用、授权撤销、到期时自行阻止旧 Key。
738
-
739
- ## 9. 联调验收清单
740
-
741
- 完整接入至少必须通过以下测试:
742
-
743
- - Discovery issuer 精确匹配、必需 endpoint 完整、PKCE `S256` 已发布;
744
- - callback 只使用 `127.0.0.1:<实际端口>/oauth/callback`;
745
- - state、nonce、PKCE verifier、ID Token 签名、audience 和有效期均被校验;
746
- - UserInfo 返回非空 `sub`,并与 ID Token 一致;`name` 可以正常显示;
747
- - 四个 Key Binding 接口全部存在,并按状态正确选择 Provision、Resolve 或 Renew;
748
- - Provision/Renew 幂等,错误重放返回 `409`;
749
- - Provider ID 在 Profile、Bootstrap 和凭据响应中完全一致;
750
- - Key Binding 的 Access Token 不能调用模型网关,模型 API Key 不能调用 UserInfo 或 Key Binding;
751
- - 运行凭据过期、撤销、账号停用和越权访问均被拒绝;
752
- - Chat Completions 可以完成至少一次流式文本对话;声明多模态或 reasoning 的模型通过对应测试;
753
- - Token、Authorization header 和 `api_key` 不出现在 URL、日志、APM、错误详情或审计数据中;
754
- - 所有失败响应均有稳定、可诊断且不泄密的错误结构。
755
-
756
- Enterprise Profile 的完整字段见 [Enterprise Profile 规范](enterprise-profile.md),OIDC 细节见 [OIDC 互操作规范](oidc-interoperability.md),Key Binding 的生命周期与安全语义见 [Key Binding 协议](key-binding-protocol.md)。
757
-
758
- ## 10. curl 联调示例(现有接口)
759
-
760
- 以下为 POSIX shell 示例,Windows 可使用 WSL。域名、client ID 和变量均是占位值;不要将真实 Token 写入工单或提交脚本。请求在运行应用的主机发起,不需要浏览器跨域 CORS 授权。`ACCESS_TOKEN` 与 `RUNTIME_KEY` 必须属于不同用途的凭据。
761
-
762
- ```bash
763
- ISSUER='https://id.example.edu/oidc'
764
- MANAGEMENT='https://ai.example.edu/api/worker/v1'
765
- MODEL_BASE='https://ai.example.edu/open/api/v1'
766
- CLIENT_ID='eduwork-public-client'
767
- REDIRECT_URI='http://127.0.0.1:3080/oauth/callback'
768
- curl --fail-with-body --max-time 20 "$ISSUER/.well-known/openid-configuration"
769
- # Copy these URLs from verified Discovery; their paths are not fixed.
770
- AUTHORIZATION_ENDPOINT='https://id.example.edu/oidc/authorize'
771
- TOKEN_ENDPOINT='https://id.example.edu/oidc/token'
772
- JWKS_URI='https://id.example.edu/oidc/jwks'
773
- USERINFO_ENDPOINT='https://id.example.edu/oidc/userinfo'
774
- REVOCATION_ENDPOINT='https://id.example.edu/oidc/revoke'
775
- curl --fail-with-body --max-time 20 "$JWKS_URI"
776
- ```
777
-
778
- Authorization URL 必须在系统浏览器中打开,不能用 curl 绕过机构登录。下方 Authorization GET 是联调请求示例,需要在浏览器完成机构交互,不是绕过登录的完整脚本;`PKCE_CHALLENGE` 必须是 `BASE64URL(SHA256(PKCE_VERIFIER))`,不是任意占位字符串。`STATE/NONCE/PKCE_VERIFIER` 由客户端使用安全随机源生成,并与本次短时监听绑定。
779
-
780
- ```bash
781
- curl --get --max-time 20 "$AUTHORIZATION_ENDPOINT" \
782
- --data-urlencode 'response_type=code' --data-urlencode "client_id=$CLIENT_ID" \
783
- --data-urlencode "redirect_uri=$REDIRECT_URI" --data-urlencode 'scope=openid profile offline_access' \
784
- --data-urlencode "state=$STATE" --data-urlencode "nonce=$NONCE" \
785
- --data-urlencode "code_challenge=$PKCE_CHALLENGE" --data-urlencode 'code_challenge_method=S256'
786
- curl --fail-with-body --max-time 20 "$TOKEN_ENDPOINT" \
787
- --data-urlencode 'grant_type=authorization_code' --data-urlencode "client_id=$CLIENT_ID" \
788
- --data-urlencode "code=$CODE" --data-urlencode "redirect_uri=$REDIRECT_URI" --data-urlencode "code_verifier=$PKCE_VERIFIER"
789
- curl --fail-with-body --max-time 20 -H "Authorization: Bearer $ACCESS_TOKEN" "$USERINFO_ENDPOINT"
790
- curl --fail-with-body --max-time 20 -H "Authorization: Bearer $ACCESS_TOKEN" "$MANAGEMENT/bootstrap"
791
- ```
792
-
793
- 只有用户已同意创建时执行 Provision。重试同一逻辑写操作时重用相同的 `IDEMPOTENCY_KEY`;Renew 的示例 Key 与 Provision 应分别生成。
794
-
795
- ```bash
796
- curl --fail-with-body --max-time 20 "$MANAGEMENT/runtime-credential/provision" \
797
- -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
798
- -H "Idempotency-Key: $IDEMPOTENCY_KEY" --data '{"provider_id":"example-ai"}'
799
- curl --fail-with-body --max-time 20 "$MANAGEMENT/runtime-credential/resolve" \
800
- -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
801
- --data '{"provider_id":"example-ai","api_key_id":"key-123"}'
802
- curl --fail-with-body --max-time 20 "$MANAGEMENT/runtime-credential/renew" \
803
- -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
804
- -H "Idempotency-Key: $RENEW_IDEMPOTENCY_KEY" --data '{"provider_id":"example-ai","api_key_id":"key-123"}'
805
- curl --fail-with-body --max-time 20 -H "Authorization: Bearer $RUNTIME_KEY" "$MODEL_BASE/models"
806
- curl --no-buffer --fail-with-body "$MODEL_BASE/chat/completions" \
807
- -H "Authorization: Bearer $RUNTIME_KEY" -H 'Content-Type: application/json' \
808
- --data '{"model":"example-model","messages":[{"role":"user","content":"Reply with OK"}],"stream":true}'
809
- curl --fail-with-body --max-time 20 "$TOKEN_ENDPOINT" \
810
- --data-urlencode 'grant_type=refresh_token' --data-urlencode "client_id=$CLIENT_ID" --data-urlencode "refresh_token=$REFRESH_TOKEN"
811
- curl --fail-with-body --max-time 10 "$REVOCATION_ENDPOINT" \
812
- --data-urlencode "client_id=$CLIENT_ID" --data-urlencode "token=$REFRESH_TOKEN"
813
- ```
814
-
815
- 所有 OIDC endpoint URL 均取自已验证的 Discovery,示例路径不是强制约定;未声明撤销端点时跳过最后一步。Scope 可按第 5.1 节增补服务端已部署的授权名称。这里只展示请求格式;Bootstrap 决定执行 Provision、Resolve、Renew 中哪一个,不应依次创建、读取和续期。Authorization Code 交换由真正发起授权的客户端执行,不能从他人浏览器复制 code 到另一个缺少原 verifier 的进程。ID Token 必须进行第 4 节全部校验,不能仅 base64 解码后相信其中的 subject。
816
-
817
- ## 11. 可选扩展提案(未实现、不会自动启用)
5
+ 当前客户端消费两种显式网关契约。服务端发现声明与真实端点行为必须一致;客户端不会按品牌、URL 猜测 Token 含义,也不会自动退回旧模型 Key 流程。
818
6
 
819
- ### 11.1 机构资源发现
7
+ - [LiteLLM native contract 1](gateway-auth/README.md):动态注册、Code + PKCE、Token 模型调用、刷新与撤销。
8
+ - [oidc-llm 0.1 实验](gateway-auth/experimental-oidc-llm.md):显式注册方式、身份模式、模型资源、公开 API 基址和作用域;仍为草案。
9
+ - [标准 OIDC 身份互操作](oidc-interoperability.md):仅身份配置不提供模型资源。
820
10
 
821
- 当前公版只读取本机可信 JSONC/Profile;**不会请求** `/.well-known/eduwork-configuration`。后续可约定此 HTTPS 入口返回纯数据 `protocol_version: eduwork-discovery/v1alpha1`,包含机构展示、OIDC Public Client、资源基址和能力名称。必须由用户/发行方明确导入,核对 issuer/provider/origin 后转换成本机配置;不得自动更换凭据目标,不得下发插件、脚本、CSS、可执行命令或任意模型 adapter。具体字段、签名/信任根、版本协商另行评审,不能把该提案当作当前协议的必需接口。
11
+ ## OIDC 模式要求
822
12
 
823
- ### 11.2 机构活跃心跳
13
+ 服务端提供匹配的 issuer、授权/Token/JWKS/UserInfo 端点,支持 public client 与 S256 PKCE。客户端验证 state、ID Token 签名和身份,再检查 UserInfo sub 一致性。发现声明支持授权响应 iss 时,回调也必须提供与 issuer 一致的值。
824
14
 
825
- 心跳不属于公共协议。机构插件自行管理其私有 wire 和显式启用配置,不能作为登录、模型或 Token 刷新的前置条件。公版不调度遥测请求。
15
+ 模型目录和调用接受相同获准 Access Token。API 地址和资源边界来自已验证的发现,服务端执行用户权限、模型访问与配额规则。模型目录采用 data[].id,调用复用 OpenAI-compatible JSON/SSE。
826
16
 
827
- ### 11.3 远端注销与模型能力扩展
17
+ Token 响应提供有效期和刷新凭据;刷新不得改变已授权主体和上下文。客户端不要求退出后 Access Token 立即失效,退出时清理本地并尽力撤销 Refresh Token。
828
18
 
829
- RP-Initiated Logout、远端模型 Key 撤销、动态客户端注册、跨设备会话管理与远程模型能力声明不在当前客户端 wire 内。不要自行创建同名“已支持”端点,或要求现有用户必须实现它们。未来加入时需要独立 capability/version、授权范围、兼容测试与数据保留规则。
19
+ ## 配额及兼容
830
20
 
831
- ## 12. 规范依据与版本规则
21
+ 公共包不定义统一配额、团队管理或 Key Binding 协议。机构可以通过[账户扩展](account-extensions.md)复用当前授权的 Host GET 传输及既有配额解析逻辑。
832
22
 
833
- - [OIDC Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) 定义身份、Token 与 UserInfo;本实现的 RS256/UserInfo 要求是互操作子集,不能推断支持所有 OIDC 选项。
834
- - [RFC 8252](https://www.rfc-editor.org/rfc/rfc8252) 定义原生应用外部浏览器与 loopback 回调;[RFC 9700](https://www.rfc-editor.org/rfc/rfc9700) 提供 OAuth 安全实践。
835
- - `dsh-oidc/v1alpha1` 是本机 Profile schema;`worker.user-center.v1` / `worker-user-center/v1` / `eduwork-resources/v1` 是相同资源 wire 的兼容标识;npm 插件版本和 DSH 宿主版本各自独立。心跳草案名称不是对已发布资源 wire 的自动升级。
836
- - 新增可选字段应保持旧客户端可忽略;不得重解释现有字段或把数量 null 改成 0。破坏性变更须新版本标识及迁移方案,未知 `protocol_version` 的拒绝不能伪装为身份登录失败。
23
+ 服务端可保留旧 Key Binding 端点服务老客户端。新客户端按[迁移说明](key-binding-protocol.md)使用 Token 直连,不调用旧取 Key 接口。[模型目录 OpenAPI](../protocol/resources.openapi.yaml)仅描述资源表面,不替代各网关认证契约。