在 ASP.NET Core 中映射、自定义和转换声明
你可以根据任何用户或 identity 数据创建声明,并使用受信任的 identity 提供者或 ASP.NET Core identity 发出声明。 声明是一个名称值对,表示使用者是什么,而不是使用者可以做什么。 本文涵盖以下几个方面:
- 如何使用 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
方法指定所需的声明,否则客户端应用中只有 name
、given_name
和 email
标准声明可用。 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.
注意
如果标识提供者的发现文档显示支持 PAR,则默认的 Open ID Connect 处理程序就会使用推送授权请求 (PAR)。 identity 提供者的发现文档通常位于 .well-known/openid-configuration
。 如果无法在 identity 提供者的客户端配置中使用 PAR,则可以使用 PushedAuthorizationBehavior 选项禁用 PAR。
builder.Services
.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("oidc", oidcOptions =>
{
// Other provider-specific configuration goes here.
// The default value is PushedAuthorizationBehavior.UseIfAvailable.
// 'OpenIdConnectOptions' does not contain a definition for 'PushedAuthorizationBehavior'
// and no accessible extension method 'PushedAuthorizationBehavior' accepting a first argument
// of type 'OpenIdConnectOptions' could be found
oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable;
});
若要确保只有在使用 PAR 时身份验证才能成功,请改用 PushedAuthorizationBehavior.Require。 此更改还将新的 OnPushAuthorization 事件引入到 OpenIdConnectEvents,可用于自定义推送的授权请求或手动处理该请求。 有关更多详细信息,请参阅 API 建议。
名称声明和角色声明映射
名称声明和角色声明映射到 ASP.NET Core HTTP 上下文中的默认属性。 有时需要对默认属性使用不同的声明,否则,名称声明和角色声明与默认值不匹配。 可以使用 TokenValidationParameters 属性映射声明,并根据需要将其设置为任何声明。 声明中的值可以直接在 HttpContext User.Identity.Name 属性和角色中使用。
如果 User.Identity.Name
没有值或缺少角色,请检查返回的声明中的值并设置 NameClaimType
和 RoleClaimType
值。 可以在 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>();
映射来自外部 identity 提供者的声明
请参阅以下文档:
你可以根据任何用户或 identity 数据创建声明,并使用受信任的 identity 提供者或 ASP.NET Core identity 发出声明。 声明是一个名称值对,表示使用者是什么,而不是使用者可以做什么。 本文涵盖以下几个方面:
- 如何使用 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
方法指定所需的声明,否则客户端应用程序中只有 name
、given_name
和 email
标准声明可用。 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
没有值或缺少角色,请检查返回的声明中的值并设置 NameClaimType
和 RoleClaimType
值。 可以在 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
映射来自外部 identity 提供者的声明
请参阅以下文档: