ASP.NET Core OIDC客户端如何将given_name存入Claims?
问题描述
我有一个ASP.NET Core应用,使用以下配置实现OpenIdConnect认证:
builder.Services.AddAuthentication() .AddOpenIdConnect( "oidc", o => { builder.Configuration.GetSection("Identity").Bind(o); o.ClientId = "webui"; o.ClientSecret = "****"; o.CallbackPath = "/cb_openIdConnect"; o.SaveTokens = true; o.ResponseType = OpenIdConnectResponseType.Code; o.GetClaimsFromUserInfoEndpoint = true; o.Scope.Add("email"); o.Scope.Add("profile"); o.UsePkce = true; o.ClaimActions.MapUniqueJsonKey("given_name", "given_name"); });
我的IDP服务器是自行管控的OpenIddict服务器。尽管已启用GetClaimsFromUserInfoEndpoint选项,但无法将UserInfo端点返回的Claims持久化到本地令牌中。
添加OnUserInformationReceived事件后,可以看到userinfo中返回了given_name:
o.Events.OnUserInformationReceived = e => { Console.WriteLine(e.User.ToString()); return Task.CompletedTask; };
输出内容:
{ "sub": "****", "given_name": "***", "family_name": "****" }
但通过AuthenticationStateProvider获取的UserPrincipal的Claims中找不到given_name。我尝试在事件中手动添加测试值,同样无法在页面的Claims列表中显示:
o.Events.OnUserInformationReceived = e => { e.Principal?.AddClaim("given_name", "wef"); return Task.CompletedTask; };
无论如何尝试,Claims始终只包含以下类型:
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress AspNet.Identity.SecurityStamp
如何将OIDC UserInfo端点的Claims存入HttpContext的UserPrincipal中,以便在网页上显示?
解决方案
1. 修正Claims映射规则
你已添加的MapUniqueJsonKey可以调整为使用标准Claim类型Uri,避免名称不匹配问题:
// 替换原有映射代码 o.ClaimActions.MapUniqueJsonKey(ClaimTypes.GivenName, "given_name"); o.ClaimActions.MapUniqueJsonKey(ClaimTypes.Surname, "family_name");
2. 配置Cookie认证保留自定义Claims
默认Cookie认证中间件会过滤部分Claims,需要显式配置保留所需字段:
builder.Services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = "oidc"; }) .AddCookie(options => { options.Cookie.Name = "WebUICookie"; options.Events.OnSigningIn = context => { // 确保UserInfo返回的Claims被添加到Cookie身份中 var givenName = context.Principal.FindFirst("given_name") ?? context.Principal.FindFirst(ClaimTypes.GivenName); var familyName = context.Principal.FindFirst("family_name") ?? context.Principal.FindFirst(ClaimTypes.Surname); if (givenName != null && !context.Principal.Claims.Any(c => c.Type == ClaimTypes.GivenName)) { context.Principal.Identities.First().AddClaim(givenName); } if (familyName != null && !context.Principal.Claims.Any(c => c.Type == ClaimTypes.Surname)) { context.Principal.Identities.First().AddClaim(familyName); } return Task.CompletedTask; }; }) .AddOpenIdConnect("oidc", o => { // 原有配置保留,替换映射规则 builder.Configuration.GetSection("Identity").Bind(o); o.ClientId = "webui"; o.ClientSecret = "****"; o.CallbackPath = "/cb_openIdConnect"; o.SaveTokens = true; o.ResponseType = OpenIdConnectResponseType.Code; o.GetClaimsFromUserInfoEndpoint = true; o.Scope.Add("email"); o.Scope.Add("profile"); o.UsePkce = true; o.ClaimActions.MapUniqueJsonKey(ClaimTypes.GivenName, "given_name"); o.ClaimActions.MapUniqueJsonKey(ClaimTypes.Surname, "family_name"); });
3. 禁用默认Claims过滤
如果仍有问题,可以清空默认的ClaimActions,只保留自己需要的映射:
o.ClaimActions.Clear(); // 手动映射所有需要的Claims o.ClaimActions.MapUniqueJsonKey(ClaimTypes.NameIdentifier, "sub"); o.ClaimActions.MapUniqueJsonKey(ClaimTypes.Name, "name"); o.ClaimActions.MapUniqueJsonKey(ClaimTypes.Email, "email"); o.ClaimActions.MapUniqueJsonKey(ClaimTypes.GivenName, "given_name"); o.ClaimActions.MapUniqueJsonKey(ClaimTypes.Surname, "family_name");
4. 检查OpenIddict服务器配置
确认OpenIddict服务器的客户端webui已授权profile作用域,且UserInfo端点确实返回了given_name和family_name字段(可通过Postman直接调用UserInfo接口验证)。
内容的提问来源于stack exchange,提问作者Brad
相关产品推荐
相关产品推荐

