ASP.NET Core 中的對應、自訂和轉換宣告

作者 Damien Bowden

宣告可以從任何使用者或身分識別資料建立,這些資料可以使用受信任的識別提供者或 ASP.NET 核心身分識別來發行。 宣告是成對的名稱和數值,代表主體的身分,而不是主體可以執行的動作。 本文涵蓋下列區域:

  • 如何使用 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 核心身分識別來發行。 宣告是成對的名稱和數值,代表主體的身分,而不是主體可以執行的動作。 本文涵蓋下列區域:

  • 如何使用 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 中擴充或新增自訂宣告

請參閱下列文件:

將宣告新增至 Identity 使用 IUserClaimsPrincipalFactory

對應來自外部識別提供者的宣告

請參閱下列文件:

在 ASP.NET Core 中保存外部提供者的其他宣告和權杖