You need to enable JavaScript to run this app. .NET Core OpenIdConnect中如何为Jwt Token添加自定义声明避免API端重复查库
优惠活动
大模型
产品
解决方案
定价
更多

.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认证流程中做同样的补全逻辑,同时加缓存降低数据库压力:

  • 操作逻辑:
    1. 给API的JwtBearer认证配置添加OnTokenValidated事件,写和你UI侧完全一致的逻辑:解析出用户的objectId,查询业务数据库获取自定义角色声明
    2. 加缓存优化:用ASP.NET Core内置的IMemoryCache(单实例API)或者IDistributedCache(多实例API集群)做缓存,key用用户的objectId,缓存有效期和JWT的过期时间保持一致
  • 参考实现代码:
// 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.24 03:45:04