在 ASP.NET Core 中映射、自定义和转换声明

作者:Damien Bowden

你可以根据任何用户或标识数据创建声明,并使用受信任的标识提供者或 ASP.NET Core 标识发出声明。 声明是一个名称值对,表示使用者是什么,而不是使用者可以做什么。 本文涵盖以下几个方面:

  • 如何使用 OpenID Connect 客户端配置和映射声明
  • 设置名称和角色声明
  • 重置声明命名空间
  • 使用 TransformAsync 自定义、扩展声明

使用 OpenID Connect 身份验证映射声明

配置文件声明可以在 id_token 中返回,后者在身份验证成功后返回。 ASP.NET Core 客户端应用只需要配置文件范围。 使用 id_token 返回声明时,不需要进行额外的声明映射。

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-your-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.Scope.Add("profile");
       options.SaveTokens = true;
   });

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();

app.Run();

上述代码需要 Microsoft.AspNetCore.Authentication.OpenIdConnect NuGet 包。

获取用户声明的另一种方法是使用 OpenID Connect 用户信息 API。 ASP.NET Core 客户端应用使用 GetClaimsFromUserInfoEndpoint 属性配置此设置。 它与第一个设置的重要区别在于,必须使用 MapUniqueJsonKey 方法指定所需的声明,否则客户端应用中只有 namegiven_nameemail 标准声明可用。 id_token 中包含的声明按默认值进行映射。 这是与第一个选项的主要区别所在。 你必须显式定义所需的某些声明。

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.Scope.Add("profile");
       options.SaveTokens = true;
       options.GetClaimsFromUserInfoEndpoint = true;
       options.ClaimActions.MapUniqueJsonKey("preferred_username",
                                             "preferred_username");
       options.ClaimActions.MapUniqueJsonKey("gender", "gender");
   });

var app = builder.Build();

// Code removed for brevity.

名称声明和角色声明映射

名称声明和角色声明映射到 ASP.NET Core HTTP 上下文中的默认属性。 有时需要对默认属性使用不同的声明,否则,名称声明和角色声明与默认值不匹配。 可以使用 TokenValidationParameters 属性映射声明,并根据需要将其设置为任何声明。 声明中的值可以直接在 HttpContext User.Identity.Name 属性和角色中使用。

如果 User.Identity.Name 没有值或缺少角色,请检查返回的声明中的值并设置 NameClaimTypeRoleClaimType 值。 可以在 HTTP 上下文中查看客户端身份验证返回的声明。

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
  .AddCookie()
  .AddOpenIdConnect(options =>
  {
       // Other options...
       options.TokenValidationParameters = new TokenValidationParameters
       {
          NameClaimType = "email"
          //, RoleClaimType = "role"
       };
  });

声明命名空间、默认命名空间

ASP.NET Core 添加了一些已知声明的默认命名空间,应用中可能不需要这些命名空间。 你可以选择禁用这些添加的命名空间并使用 OpenID Connect 服务器创建的确切声明。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

JsonWebTokenHandler.DefaultInboundClaimTypeMap.Clear();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-your-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.Scope.Add("profile");
       options.SaveTokens = true;
   });

var app = builder.Build();

// Code removed for brevity.
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-your-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.Scope.Add("profile");
       options.SaveTokens = true;
   });

var app = builder.Build();

// Code removed for brevity.

如果需要按方案禁用命名空间而不是进行全局禁用,则可以使用“MapInboundClaims = false”选项。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-your-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.MapInboundClaims = false;
       options.Scope.Add("profile");
       options.SaveTokens = true;
   });

var app = builder.Build();

// Code removed for brevity.

使用 IClaimsTransformation 扩展或添加自定义声明

IClaimsTransformation 接口可用于向 ClaimsPrincipal 类添加额外的声明。 该接口需要一个 TransformAsync 方法。 此方法可能会被多次调用。 仅当 ClaimsPrincipal 中没有时才添加新声明。 系统将创建 ClaimsIdentity 以添加新声明,并且可以将其添加到 ClaimsPrincipal 中。

using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;

public class MyClaimsTransformation : IClaimsTransformation
{
    public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        ClaimsIdentity claimsIdentity = new ClaimsIdentity();
        var claimType = "myNewClaim";
        if (!principal.HasClaim(claim => claim.Type == claimType))
        {
            claimsIdentity.AddClaim(new Claim(claimType, "myClaimValue"));
        }

        principal.AddIdentity(claimsIdentity);
        return Task.FromResult(principal);
    }
}

IClaimsTransformation 接口和 MyClaimsTransformation 类可以注册为服务:

builder.Services.AddTransient<IClaimsTransformation, MyClaimsTransformation>();

映射来自外部标识提供者的声明

请参阅以下文档:

在 ASP.NET Core 中保留来自外部提供程序的附加声明和令牌

你可以根据任何用户或标识数据创建声明,并使用受信任的标识提供者或 ASP.NET Core 标识发出声明。 声明是一个名称值对,表示使用者是什么,而不是使用者可以做什么。 本文涵盖以下几个方面:

  • 如何使用 OpenID Connect 客户端配置和映射声明
  • 设置名称和角色声明
  • 重置声明命名空间
  • 使用 TransformAsync 自定义、扩展声明

使用 OpenID Connect 身份验证映射声明

配置文件声明可以在 id_token 中返回,后者在身份验证成功后返回。 ASP.NET Core 客户端应用只需要配置文件范围。 使用 id_token 返回声明时,不需要进行额外的声明映射。

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-your-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.Scope.Add("profile");
       options.SaveTokens = true;
   });

获取用户声明的另一种方法是使用 OpenID Connect 用户信息 API。 ASP.NET Core 客户端应用程序使用 GetClaimsFromUserInfoEndpoint 属性配置此设置。 它与第一个设置的重要区别在于,你必须使用 MapUniqueJsonKey 方法指定所需的声明,否则客户端应用程序中只有 namegiven_nameemail 标准声明可用。 id_token 中包含的声明按默认值进行映射。 这是与第一个选项的主要区别所在。 你必须显式定义所需的某些声明。

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-your-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.Scope.Add("profile");
       options.SaveTokens = true;
       options.GetClaimsFromUserInfoEndpoint = true;
       options.ClaimActions.MapUniqueJsonKey("preferred_username", "preferred_username");
       options.ClaimActions.MapUniqueJsonKey("gender", "gender");
   }); 

名称声明和角色声明映射

名称声明和角色声明映射到 ASP.NET Core HTTP 上下文中的默认属性。 有时需要对默认属性使用不同的声明,否则,名称声明和角色声明与默认值不匹配。 可以使用 TokenValidationParameters 属性映射声明,并根据需要将其设置为任何声明。 声明中的值可以直接在 HttpContext User.Identity.Name 属性和角色中使用。

如果 User.Identity.Name 没有值或缺少角色,请检查返回的声明中的值并设置 NameClaimTypeRoleClaimType 值。 可以在 HTTP 上下文中查看客户端身份验证返回的声明。

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       // other options...
       options.TokenValidationParameters = new TokenValidationParameters
       {
         NameClaimType = "email", 
         // RoleClaimType = "role"
       };
   });

声明命名空间、默认命名空间

ASP.NET Core 添加了一些已知声明的默认命名空间,应用中可能不需要这些命名空间。 你可以选择禁用这些添加的命名空间并使用 OpenID Connect 服务器创建的确切声明。

public void Configure(IApplicationBuilder app)
{
    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

使用 IClaimsTransformation 扩展或添加自定义声明

IClaimsTransformation 接口可用于向 ClaimsPrincipal 类添加额外的声明。 该接口需要一个 TransformAsync 方法。 此方法可能会被多次调用。 仅当 ClaimsPrincipal 中没有时才添加新声明。 系统将创建 ClaimsIdentity 以添加新声明,并且可以将其添加到 ClaimsPrincipal 中。

public class MyClaimsTransformation : IClaimsTransformation
{
    public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
       ClaimsIdentity claimsIdentity = new ClaimsIdentity();
       var claimType = "myNewClaim";
       if (!principal.HasClaim(claim => claim.Type == claimType))
       {		   
          claimsIdentity.AddClaim(new Claim(claimType, "myClaimValue"));
       }

       principal.AddIdentity(claimsIdentity);
       return Task.FromResult(principal);
    }
}

IClaimsTransformation 接口和 MyClaimsTransformation 类可以作为服务添加到 ConfigureServices 方法中。

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IClaimsTransformation, MyClaimsTransformation>();

在 ASP.NET Core 中扩展或添加自定义声明Identity

请参阅以下文档:

使用 IUserClaimsPrincipalFactory 添加声明到 Identity

映射来自外部标识提供者的声明

请参阅以下文档:

在 ASP.NET Core 中保留来自外部提供程序的附加声明和令牌