.NET Core OpenIdConnect中如何为Jwt Token添加自定义声明避免API端重复查库
你当前在UI侧
OnTokenValidated中添加的自定义声明,仅会保存在UI应用本地的登录用户凭证(比如ASP.NET Core的认证Cookie)里,用来支撑UI侧的按钮显隐等本地授权判断。而调用API时传递的是你们自定义AD最初颁发的原始JWT,这个令牌的Payload里根本没有你后期添加的superadmin这类自定义声明,因此API端解析令牌时只能拿到AD原生声明,只能再次查询数据库补全信息。
下面提供3种可行方案,按优先级排序:
方案一:最优解:在自定义AD(身份提供方)的令牌签发流程中注入自定义声明
因为你们的AD是企业自建的,完全可以从根源解决这个问题:
- 操作逻辑:在AD服务生成访问令牌、ID令牌的代码逻辑中,拿到当前认证用户的objectId后,直接查询业务数据库,把对应的自定义角色、专属声明写入JWT的Payload中
- 优势:令牌本身就携带全量声明,UI、API、所有其他下游服务拿到令牌就能直接用,不需要任何应用端再补查库,一次注入全局生效
- 注意点:
- 控制JWT大小,自定义声明只放必要的角色/权限标识,不要塞大段的用户信息,避免超出HTTP请求头的大小限制
- 如果你们的自建AD是基于OpenIddict、IdentityServer这类框架搭建的,直接在框架提供的Claims生成扩展点(比如
IProfileService实现)里加查库逻辑即可,不需要改动核心令牌签发代码
方案二:无法修改AD服务时,API端校验令牌时补全声明+加缓存
如果自定义AD是其他团队维护、改动成本太高无法修改,可以在API端的JWT认证流程中做同样的补全逻辑,同时加缓存降低数据库压力:
- 操作逻辑:
- 给API的JwtBearer认证配置添加
OnTokenValidated事件,写和你UI侧完全一致的逻辑:解析出用户的objectId,查询业务数据库获取自定义角色声明 - 加缓存优化:用ASP.NET Core内置的
IMemoryCache(单实例API)或者IDistributedCache(多实例API集群)做缓存,key用用户的objectId,缓存有效期和JWT的过期时间保持一致
- 给API的JwtBearer认证配置添加
- 参考实现代码:
// API端的JwtBearer认证配置 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // 原有JWT校验参数配置 options.TokenValidationParameters = new TokenValidationParameters { /* ... */ }; // 自定义声明补全逻辑 options.Events.OnTokenValidated = async ctx => { string oid = ctx.Principal.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier"); var cache = ctx.HttpContext.RequestServices.GetRequiredService<IMemoryCache>(); // 先查缓存,避免重复查库 if (!cache.TryGetValue<List<Claim>>($"user_custom_claims_{oid}", out var customClaims)) { var db = ctx.HttpContext.RequestServices.GetRequiredService<AuthorizationDbContext>(); bool isSuperAdmin = await db.SuperAdmins.AnyAsync(a => a.ObjectId == oid); customClaims = new List<Claim>(); if (isSuperAdmin) { customClaims.Add(new Claim(ClaimTypes.Role, "superadmin")); } // 缓存过期时间和JWT保持一致,避免拿到过期权限 cache.Set($"user_custom_claims_{oid}", customClaims, TimeSpan.FromHours(1)); } // 把自定义声明加入当前用户身份上下文 if (customClaims.Any()) { ctx.Principal.AddIdentity(new ClaimsIdentity(customClaims)); } }; });
- 注意点:
- 如果用户的角色权限发生变更,需要主动删除缓存中对应objectId的key,否则用户要等到JWT过期才能拿到新的权限
- 多实例部署的API一定要用分布式缓存,避免不同实例的缓存数据不一致
方案三:BFF层统一补全声明(适配前后端分离架构)
如果你的架构是「前端SPA + BFF后端 + 微服务API」的模式,也可以直接在BFF层(也就是你现在写OnTokenValidated的地方)统一处理:
- 操作逻辑:在BFF校验完AD返回的令牌、补全自定义声明之后,基于现有身份生成一个新的JWT(或者走令牌交换流程),再把这个带全量声明的JWT传递给下游API
- 优势:不需要修改每个API的认证逻辑,所有下游服务统一信任BFF签发的令牌即可
- 注意点:BFF签发的令牌的签名密钥、校验规则要和下游API对齐,避免出现信任漏洞
特别提醒:不要为了省事把UI侧补的自定义声明直接放在请求头里传给API,所有权限类声明必须由可信的后端服务生成、校验,避免被恶意篡改提权。
内容的提问来源于stack exchange,提问作者A.Learn
相关产品推荐
相关产品推荐

