Node-oidc-provider如何实现客户端列表数据库存储
node-oidc-provider 数据库存储客户端列表实现方案
node-oidc-provider 本身不绑定客户端存储介质,通过内置的Adapter适配器模式支持自定义存储,不需要修改框架源码,按以下步骤实现即可。
1. 编写Client对应的数据库适配器
框架所有模型(包括Client、AccessToken、AuthorizationCode等)的读写都会走你传入的适配器,你不需要给所有模型实现数据库逻辑,单独处理Client模型即可,其他模型默认走内存存储。
适配器必须实现find、upsert、destroy三个核心方法,其余方法对Client模型无实际作用,空实现即可。
参考实现:
// 引入你自己业务层的客户端数据库操作类 const ClientDao = require('./your-project/dao/client'); class DbAdapter { constructor(modelName) { this.modelName = modelName; } // 根据client_id查询客户端配置 async find(clientId) { if (this.modelName !== 'Client') return undefined; const dbClient = await ClientDao.findOne({ client_id: clientId }); if (!dbClient) return undefined; // 注意返回的字段必须和oidc-provider要求的客户端配置字段完全匹配 return { client_id: dbClient.client_id, client_secret: dbClient.client_secret, redirect_uris: dbClient.redirect_uris, grant_types: dbClient.grant_types, response_types: dbClient.response_types, token_endpoint_auth_method: dbClient.token_endpoint_auth_method, // 其余业务需要的客户端配置字段,和官方客户端配置项一一对应即可 }; } // 新增/更新客户端 async upsert(clientId, clientConfig, expiresIn) { if (this.modelName !== 'Client') return; // 客户端一般为永久有效,expiresIn字段可忽略 await ClientDao.updateOne( { client_id: clientId }, { $set: clientConfig }, { upsert: true } ); } // 删除客户端 async destroy(clientId) { if (this.modelName !== 'Client') return; await ClientDao.deleteOne({ client_id: clientId }); } // 以下方法为其他令牌类模型使用,Client模型不需要实现,空函数即可 async findByUserCode() {} async consume() {} }
2. 注入适配器到Provider实例
初始化oidc-provider的时候,将编写好的适配器传入配置项即可:
const { Provider } = require('oidc-provider'); const oidcProvider = new Provider('https://your-oidc-domain.com', { // 其余你的业务配置:features、ttl、claims、cookies等 adapter: DbAdapter, // 如果你有少量固定不变的客户端,也可以同时配置静态clients,静态配置优先级高于适配器查询结果 // clients: [] });
常见踩坑说明
- 不要直接返回数据库中驼峰命名的字段,必须转换为框架要求的下划线命名格式(比如
redirectUris要转为redirect_uris),否则会直接抛客户端无效错误,且不会提示具体错误字段,排查成本很高 - 动态新增、修改、删除客户端不需要重启服务,框架每次校验客户端时都会实时调用find方法查询,配置变更即时生效
- 流量较高的场景可以在find方法中加一层短TTL缓存(比如30s~1min),减少数据库查询压力,客户端配置变更时主动清除对应缓存即可
- 如果后续需要把令牌、会话等数据也存数据库,直接在适配器里扩展对应modelName的处理逻辑即可,不需要修改框架其他配置
内容的提问来源于stack exchange,提问作者manhh
相关产品推荐
相关产品推荐

