You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Duende IdentityServer:AddJwtBearer无法获取自定义声明,AddOpenIdConnect却正常

问题分析与解决方案

你的核心矛盾是:自定义用户声明(nickname、city、country)放在IdentityResource里时,Razor客户端能通过UserInfo端点拿到,但API的Access Token里没有;改成ApiScope后,API能拿到,但UI客户端又获取不到。本质是IdentityResource和ApiScope的声明分发逻辑不同:

  • IdentityResource的声明默认只会出现在ID Token或UserInfo响应中,不会自动进入Access Token
  • ApiScope的声明默认只会出现在Access Token中,不会进入ID Token或UserInfo响应

要实现UI和API同时获取到这些自定义声明,需要同时配置两种资源类型,并调整ProfileService的逻辑确保声明能分发到对应的令牌中。

具体实现步骤

1. 同时配置IdentityResource和ApiScope

在Identity Server的配置中,保留原有的myApi.Data身份资源(供UI获取),新增一个对应的API范围(供API获取):

// 身份资源:供UI客户端通过UserInfo或ID Token获取声明
IEnumerable<IdentityResource> IdentityResources =
    new List<IdentityResource>
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        new IdentityResource("myApi.Data", new [] {   
            nameof(User.NickName),
            nameof(User.Country),
            nameof(User.City)
        })
    };

// API范围:供API从Access Token中获取声明
IEnumerable<ApiScope> ApiScopes =
    new List<ApiScope>
    {
        new ApiScope("api1", "My API"),
        new ApiScope(IdentityServerConstants.LocalApi.ScopeName),
        // 新增包含自定义声明的API范围
        new ApiScope("api1.data", new [] {
            nameof(User.NickName),
            nameof(User.Country),
            nameof(User.City)
        })
    };

// 更新客户端允许的范围,同时包含身份资源和API范围
IEnumerable<Client> Clients =
new List<Client>
{
    new Client
    {
        ClientId = "web",
        ClientSecrets = { new Secret("secret".Sha256()) },
        AllowedGrantTypes = GrantTypes.Code,
        RedirectUris = { "https://localhost:7070/signin-oidc" },
        PostLogoutRedirectUris = { "https://localhost:7070/signout-callback-oidc" },
        AllowOfflineAccess = true,
        AllowedScopes = new List<string>
        {
            IdentityServerConstants.StandardScopes.OpenId,
            IdentityServerConstants.StandardScopes.Profile,
            "api1",
            "myApi.Data",   // UI用的身份资源
            "api1.data",    // API用的范围
            IdentityServerConstants.LocalApi.ScopeName
        }
    }
};

2. 调整CustomProfileService逻辑

修改GetProfileDataAsync方法,确保自定义声明能根据请求的资源类型,分别添加到身份令牌(供UI)和访问令牌(供API)中:

public class CustomProfileService : IProfileService
{
    private readonly UserManager<User> _userManager;

    public CustomProfileService(UserManager<User> userManager)
    {
        _userManager = userManager;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var user = await _userManager.GetUserAsync(context.Subject);
        if (user == null)
            throw new ArgumentException("用户不存在");

        // 准备自定义声明集合
        var customClaims = new List<Claim>
        {
            new Claim(nameof(User.NickName), user.NickName ?? string.Empty),
            new Claim(nameof(User.Country), user.Country ?? string.Empty),
            new Claim(nameof(User.City), user.City ?? string.Empty)
        };

        // 如果请求包含身份资源myApi.Data,将声明添加到身份令牌/用户信息响应
        if (context.RequestedResources.IdentityResources.Any(r => r.Name == "myApi.Data"))
        {
            context.IssuedClaims.AddRange(customClaims);
        }

        // 如果请求包含API范围api1.data,将声明添加到访问令牌
        if (context.RequestedResources.ApiScopes.Any(s => s.Name == "api1.data"))
        {
            context.IssuedClaims.AddRange(customClaims);
        }

        // 补充基础用户声明(可选)
        var baseClaims = new List<Claim>
        {
            new Claim(JwtClaimTypes.Name, user.UserName)
        };
        context.IssuedClaims.AddRange(baseClaims);
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        var user = await _userManager.GetUserAsync(context.Subject);
        context.IsActive = user != null;
    }
}

3. 客户端与API的配置确认

  • Razor客户端:现有配置已经包含myApi.Data scope,且开启了GetClaimsFromUserInfoEndpoint = true,无需修改,仍能通过User.Claims拿到自定义声明。
  • API端:现有JwtBearer配置无需修改,只要客户端请求Access Token时包含api1.data scope,API就能从User.Claims中解析出这些自定义声明。

原理说明

  • Identity Server会根据请求的资源类型(IdentityResource/ApiScope),自动筛选ProfileService中添加的声明,分别注入到对应的令牌中。
  • 这种方式既保证了UI客户端通过标准的OIDC流程获取用户身份信息,也让API能从Access Token中直接拿到所需的用户数据,避免了API额外调用UserInfo端点的开销。

内容的提问来源于stack exchange,提问作者Style

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 17:18:08