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:
@@ -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 私钥(RS256,PEM 格式,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)
|
||||
|
||||
Reference in New Issue
Block a user