feat: 实现完整的 OIDC Provider 功能

- 后端:基于 node-oidc-provider 实现 OIDC Provider
  - 支持 authorization_code、refresh_token、client_credentials 授权类型
  - Redis adapter 存储会话数据,Prisma adapter 存储持久化数据
  - 客户端管理 CRUD API(创建、更新、删除、重新生成密钥)
  - 交互 API(登录、授权确认、中止)
  - 第一方应用自动跳过授权确认页面
  - 使用 cuid2 生成客户端 ID

- 前端:OIDC 客户端管理界面
  - 客户端列表表格(支持分页、排序)
  - 创建/编辑弹窗(支持所有 OIDC 配置字段)
  - OIDC 交互页面(登录表单、授权确认表单)

- 共享类型:添加 OIDC 相关 TypeScript 类型定义

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
charilezhou
2026-01-20 17:22:32 +08:00
parent 8db25538d4
commit 90513e8278
38 changed files with 4186 additions and 16 deletions

View File

@@ -506,9 +506,11 @@ export class OidcClientController {
## 四、OIDC 端点说明
Issuer URL 为 `http://localhost:4000/oidc`,所有标准 OIDC 端点都在 `/oidc` 路径下:
| 端点 | 方法 | 说明 |
|------|------|------|
| `/.well-known/openid-configuration` | GET | OIDC 发现文档 |
| `/oidc/.well-known/openid-configuration` | GET | OIDC 发现文档 |
| `/oidc/authorize` | GET | 授权端点 |
| `/oidc/token` | POST | 令牌端点 |
| `/oidc/userinfo` | GET/POST | 用户信息端点 |
@@ -516,9 +518,15 @@ export class OidcClientController {
| `/oidc/revoke` | POST | 令牌撤销 |
| `/oidc/introspect` | POST | 令牌内省 |
| `/oidc/logout` | GET/POST | 登出端点 |
| `/oidc/interaction/:uid` | GET | 获取交互详情 |
| `/oidc/interaction/:uid/login` | POST | 提交登录 |
| `/oidc/interaction/:uid/confirm` | POST | 提交授权确认 |
自定义交互 API 端点在 `/oidc-interaction` 路径下(避免与 oidc-provider 冲突):
| 端点 | 方法 | 说明 |
|------|------|------|
| `/oidc-interaction/:uid` | GET | 获取交互详情 |
| `/oidc-interaction/:uid/login` | POST | 提交登录 |
| `/oidc-interaction/:uid/confirm` | POST | 提交授权确认 |
| `/oidc-interaction/:uid/abort` | POST | 中止授权 |
## 五、前端改动
@@ -880,8 +888,8 @@ export const OidcScopeDescriptions: Record<string, string> = {
```bash
# ----- OIDC Provider 配置 -----
# OIDC 签发者 URL必须是可公开访问的 HTTPS URL
OIDC_ISSUER=http://localhost:4000
# OIDC 签发者 URL必须是可公开访问的 URL包含 /oidc 路径
OIDC_ISSUER=http://localhost:4000/oidc
# OIDC Cookie 签名密钥(生产环境必须修改)
OIDC_COOKIE_SECRET=your-oidc-cookie-secret-change-in-production
# OIDC JWKS 私钥RS256PEM 格式Base64 编码)
@@ -939,10 +947,87 @@ OIDC_JWKS_PRIVATE_KEY=
3. API 文档完善
4. 使用文档
## 十、关键文件清单
## 十、NestJS 集成要点
### 10.1 Issuer URL 配置
Issuer URL 可以包含路径前缀,如 `http://localhost:4000/oidc`
- 发现文档路径:`/oidc/.well-known/openid-configuration`
- 授权端点:`/oidc/authorize`
- 令牌端点:`/oidc/token`
这符合 [RFC 8414](https://tools.ietf.org/html/rfc8414) 规范,`.well-known` 路径是相对于 Issuer URL 的。
### 10.2 中间件挂载方式
**问题**NestJS Controller 的 `@All('*')` 无法正确捕获 oidc-provider 的所有路由(如 `.well-known` 路径)。
**解决方案**:使用 Express 原生方式在 `main.ts` 中挂载:
```typescript
// main.ts
const oidcService = app.get(OidcService);
const provider = oidcService.getProvider();
if (provider) {
const expressApp = app.getHttpAdapter().getInstance();
expressApp.use('/oidc', provider.callback());
}
```
### 10.3 初始化时机(关键)
**问题**`onModuleInit()` 生命周期钩子在 `NestFactory.create()` 返回后执行,但中间件需要在路由注册前挂载。
**解决方案**:将 Provider 初始化从 `onModuleInit()` 移到**构造函数**中:
```typescript
// ❌ 错误onModuleInit 执行太晚,中间件挂载时 provider 还是 undefined
@Injectable()
export class OidcService implements OnModuleInit {
async onModuleInit() {
this.provider = new Provider(issuer, config);
}
}
// ✅ 正确:构造函数中同步初始化,确保 app.get() 时 provider 已就绪
@Injectable()
export class OidcService {
constructor(...deps) {
this.initializeProvider();
}
}
```
### 10.4 挂载顺序
中间件必须在 NestJS 路由注册之前挂载,正确的启动顺序:
```
NestFactory.create()
→ 挂载 oidc-provider 中间件Express 原生方式)
→ app.useGlobalPipes()
→ app.enableCors()
→ app.listen()
```
### 10.5 与 NestJS Controller 共存
为避免路由冲突oidc-provider 和 NestJS Controller 使用不同的路径前缀:
| 处理者 | 路径前缀 | 端点 |
|--------|----------|------|
| oidc-provider | `/oidc` | 标准 OIDC 端点:`.well-known``/authorize``/token``/userinfo``/jwks` 等 |
| NestJS Controller | `/oidc-interaction` | 自定义交互 API`/:uid``/:uid/login``/:uid/confirm``/:uid/abort` |
| NestJS Controller | `/oidc-clients` | 客户端管理 API |
oidc-provider 中间件挂载在 `/oidc` 路径下,会先执行。自定义交互 API 放在 `/oidc-interaction` 路径下,由 NestJS 路由处理,避免被 oidc-provider 拦截。
## 十一、关键文件清单
| 文件 | 说明 |
|------|------|
| `apps/api/src/main.ts` | 应用入口,挂载 oidc-provider 中间件 |
| `apps/api/prisma/schema.prisma` | 添加 OIDC 相关数据模型 |
| `apps/api/src/oidc/` | OIDC 模块目录 |
| `apps/api/src/auth/auth.service.ts` | 参考现有认证逻辑 |
@@ -950,7 +1035,7 @@ OIDC_JWKS_PRIVATE_KEY=
| `apps/web/src/app/(dashboard)/oidc-clients/` | OIDC 客户端管理页面 |
| `packages/shared/src/types/oidc.ts` | OIDC 共享类型定义 |
## 十、参考资料
## 十、参考资料
- [panva/node-oidc-provider](https://github.com/panva/node-oidc-provider)
- [node-oidc-provider Documentation](https://github.com/panva/node-oidc-provider/blob/main/docs/README.md)