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

OpenIddict API服务器中ClaimsPrincipal缺失自定义声明的问题

问题:OpenIddict资源服务器无法自动获取Access Token中的自定义声明

环境配置

OpenIddict服务器(Server.csproj)

builder.Services.AddOpenIddict()
    .AddCore(options => ...)
    .AddServer(options => ...)
    .AddValidation(options => ...)
    .AddUIStore(options => ...)
    .AddUIApis(options => ...)

受保护的API服务器(Api.csproj)

builder.Services
    .AddOpenIddict()
    .AddValidation(options =>
    {
        options.SetIssuer("...");
        options
            .UseIntrospection()
            .SetClientId("xxx")
            .SetClientSecret("xxx");
        options.UseSystemNetHttp();
        options.UseAspNetCore();
    });

现象

API接口可正常完成认证,但ClaimsPrincipal中缺少Access Token里的自定义声明:

接口代码示例:

[HttpGet()]
[Authorize(Policy = "DefaultPolicy")]
public async Task<IActionResult> MyGetMethod()
{
    var accessToken = await this.HttpContext.GetTokenAsync("access_token");
    var isAuthenticated = this.HttpContext.User?.Identity?.IsAuthenticated;
    ...
}

通过Jwt.io解析access_token,可见包含目标自定义声明:

{
  "sub": "user@xxx.xxx",
  "name": "Adrien Pellegrini",
  "email": "xxx@xxx.xxx",
  "employeeId": xxx,
  "username": "user",
  "given_name": "Adrien",
  "family_name": "Pellegrini",
  "office_location": "xxx",
  "role": "a_super_role",
  "oi_prst": "MyClientId",
  "client_id": "MyClientId",
  "oi_tkn_id": "0edc928b-xxx-xxx-xxxx-xxxxe9ca",
  "scope": "openid profile email roles offline_access",
  "jti": "f9d386bf-xxx-xxx-xxx-xxxxxx29fa",
  "exp": 1686050931,
  "iss": "https://localhost:5001/",
  "iat": 1686047331
}

但ClaimsPrincipal.Claims仅包含基础声明:

"sub: user@xxx.xxx"
"jti: 9dbd9460-xxx-xxx-xxx-xxxxx34423"
"token_usage: access_token"
"client_id: MyClientId"
"iat: 1686053762"
"exp: 1686057362"
"oi_tkn_typ: access_token"
"oi_crt_dt: Tue, 06 Jun 2023 12:16:02 GMT"
"oi_exp_dt: Tue, 06 Jun 2023 13:16:02 GMT"
"oi_prst: MyClientId"

需求

希望将employeeId、username、given_name、family_name、office_location、role这些自定义声明自动包含到ClaimsPrincipal中。目前已尝试自定义验证处理程序,但需手动解析令牌,期望更自动化的实现方式:

public class MyEventHandler : IOpenIddictValidationHandler<ValidateTokenContext>
{
    public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
        = OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
            .UseScopedHandler<MyEventHandler>()
            .SetType(OpenIddictValidationHandlerType.Custom)
            .Build();

    public ValueTask HandleAsync(ValidateTokenContext context)
    {
        var principal = context.Principal;
        // use and parse context.Token
        return default;
    }
}

解决方案

方案1:服务器端配置内省端点返回自定义声明

由于当前使用**令牌内省(Introspection)**模式,OpenIddict内省端点默认仅返回标准字段,需在服务器端添加事件处理,将自定义声明注入内省响应:

.AddServer(options =>
{
    // 其他服务器配置...

    options.AddEventHandler<HandleIntrospectionRequestContext>(builder =>
    {
        builder.UseInlineHandler(context =>
        {
            var principal = context.TokenPrincipal;
            if (principal == null) return default;

            // 按需添加自定义声明到内省响应
            if (principal.HasClaim(c => c.Type == "employeeId"))
            {
                context.Response["employeeId"] = principal.FindFirst("employeeId")!.Value;
            }
            if (principal.HasClaim(c => c.Type == "username"))
            {
                context.Response["username"] = principal.FindFirst("username")!.Value;
            }
            if (principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
            {
                context.Response["given_name"] = principal.FindFirst(ClaimTypes.GivenName)!.Value;
            }
            if (principal.HasClaim(c => c.Type == ClaimTypes.Surname))
            {
                context.Response["family_name"] = principal.FindFirst(ClaimTypes.Surname)!.Value;
            }
            if (principal.HasClaim(c => c.Type == "office_location"))
            {
                context.Response["office_location"] = principal.FindFirst("office_location")!.Value;
            }
            if (principal.HasClaim(c => c.Type == ClaimTypes.Role))
            {
                context.Response["role"] = principal.FindFirst(ClaimTypes.Role)!.Value;
            }

            return default;
        });
    });
})

方案2:API客户端端配置声明映射

在API的验证配置中添加声明转换器,将内省响应中的自定义字段映射到ClaimsPrincipal:

.AddValidation(options =>
{
    options.SetIssuer("https://localhost:5001/");
    options.AddAudiences("your-api-audience"); // 替换为你的API受众

    options
        .UseIntrospection()
        .SetClientId("xxx")
        .SetClientSecret("xxx");

    // 声明转换逻辑
    options.AddClaimTransformer(context =>
    {
        var response = context.IntrospectionResponse;
        if (response == null) return default;

        if (response.TryGetValue("employeeId", out var value))
        {
            context.Principal.AddClaim(new Claim("employeeId", value.ToString()!));
        }
        if (response.TryGetValue("username", out value))
        {
            context.Principal.AddClaim(new Claim("username", value.ToString()!));
        }
        if (response.TryGetValue("given_name", out value))
        {
            context.Principal.AddClaim(new Claim(ClaimTypes.GivenName, value.ToString()!));
        }
        if (response.TryGetValue("family_name", out value))
        {
            context.Principal.AddClaim(new Claim(ClaimTypes.Surname, value.ToString()!));
        }
        if (response.TryGetValue("office_location", out value))
        {
            context.Principal.AddClaim(new Claim("office_location", value.ToString()!));
        }
        if (response.TryGetValue("role", out value))
        {
            context.Principal.AddClaim(new Claim(ClaimTypes.Role, value.ToString()!));
        }

        return default;
    });

    options.UseSystemNetHttp();
    options.UseAspNetCore();
});

方案3:切换为本地JWT验证(推荐)

如果你的Access Token是JWT格式,可直接使用本地验证模式,API会自动解析JWT并提取所有声明,无需调用内省端点:

.AddValidation(options =>
{
    options.SetIssuer("https://localhost:5001/");
    options.AddAudiences("your-api-audience");

    // 使用本地验证(直接解析JWT)
    options.UseLocalValidation();
    options.UseSystemNetHttp(); // 自动获取服务器密钥集用于签名验证
    options.UseAspNetCore();
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 17:19:54