使用IdentityServer4对接Office365认证时无法获取邮箱信息
我正在使用IdentityServer4对接Office 365进行认证,能在IdentityServer的OnUserInformationReceived事件日志中看到从Office 365获取的Token包含邮箱信息,但MVC客户端查询User对象时,邮箱Claim始终不存在。
在IdentityServer的Startup.cs中可看到邮箱信息:
options.Events.OnUserInformationReceived = ctx => { Console.WriteLine(); Console.WriteLine("Claims from the ID token"); foreach (var claim in ctx.Principal.Claims) { Console.WriteLine($"{claim.Type} - {claim.Value}"); } Console.WriteLine(); Console.WriteLine("Claims from the UserInfo endpoint"); foreach (var property in ctx.User.RootElement.EnumerateObject()) { Console.WriteLine($"{property.Name} - {property.Value}"); } return Task.CompletedTask; };
但MVC客户端Razor页面中遍历User.Claims时,无邮箱相关数据:
@foreach (var claim in User.Claims) { <tr> <td> @Html.DisplayFor(modelItem => claim.Type) </td> <td> @Html.DisplayFor(modelItem => claim.Value) </td> </tr> }
输出结果:
Claim Type Claim Value
s_hash OpiGCfrQ9Cc71rXb3iFC0Q
sid 000180029368B13BA99045D16BF07156
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier 7D6B74E7E17AFE5640B41EC0EC8E003E2B3C22117DE82D64EFA6B935EF3DD93E
auth_time 1682352357
http://schemas.microsoft.com/identity/claims/identityprovider AzureAD
http://schemas.microsoft.com/claims/authnmethodsreferences external
核心问题是IdentityServer默认不会自动将外部身份源的所有Claim传递给客户端,需通过以下步骤确保邮箱Claim正确流转:
1. 修正IdentityServer的Office 365 OIDC Claim映射
确保将Office 365返回的邮箱字段映射到IdentityServer标准Claim类型,避免类型不匹配:
builder.Services.AddAuthentication() .AddOpenIdConnect("AzureAD", "Office 365", options => { // 移除重复的SaveTokens配置 options.SaveTokens = true; options.ClaimActions.MapAllExcept("iss", "nbf", "exp", "aud", "nonce", "iat", "c_hash"); // 映射到标准JWT邮箱Claim类型 options.ClaimActions.MapUniqueJsonKey(JwtClaimTypes.Email, "email"); options.ClaimActions.MapUniqueJsonKey(JwtClaimTypes.Email, "preferred_username"); // 兼容Office 365部分返回场景 // 其余原有配置保持不变 options.GetClaimsFromUserInfoEndpoint = true; options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.ClientId = "edf44846-5e26-409e-b4de-59676a88e0c3"; options.ClientSecret = "FJd8Q~-wGZfozfIfPdb9z5W_i1fGy4xZLBNMXdjW"; options.Authority = "https://login.microsoftonline.com/44f5f615-327a-4d5a-86d5-c9251297d7e4/v2.0"; options.ResponseType = "code"; options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("email"); options.CallbackPath = "/signin-oidc"; options.TokenValidationParameters.NameClaimType = "name"; options.Events.OnUserInformationReceived = ctx => { // 原有日志逻辑保持不变 Console.WriteLine(); Console.WriteLine("Claims from the ID token"); foreach (var claim in ctx.Principal.Claims) { Console.WriteLine($"{claim.Type} - {claim.Value}"); } Console.WriteLine(); Console.WriteLine("Claims from the UserInfo endpoint"); foreach (var property in ctx.User.RootElement.EnumerateObject()) { Console.WriteLine($"{property.Name} - {property.Value}"); } return Task.CompletedTask; }; });
2. 自定义ProfileService暴露邮箱Claim
IdentityServer默认ProfileService不会将外部身份的所有Claim传递给客户端,需自定义实现:
public class CustomProfileService : IProfileService { public Task GetProfileDataAsync(ProfileDataRequestContext context) { // 获取外部身份中的邮箱Claim var emailClaims = context.Subject.Claims.Where(c => c.Type == JwtClaimTypes.Email || c.Type == "email"); context.IssuedClaims.AddRange(emailClaims); // 添加其他默认Claim var otherClaims = context.Subject.Claims.Where(c => !emailClaims.Select(ec => ec.Type).Contains(c.Type)); context.IssuedClaims.AddRange(otherClaims); return Task.CompletedTask; } public Task IsActiveAsync(IsActiveContext context) { context.IsActive = true; return Task.CompletedTask; } }
在Startup.cs中注册该服务:
builder.Services.AddScoped<IProfileService, CustomProfileService>();
3. 验证MVC客户端的Claim配置
确保MVC客户端正确识别邮箱Claim,添加Claim映射规则:
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { options.Authority = "https://localhost:5005"; options.ClientId = "edf44846-5e26-409e-b4de-59676a88e0c3"; options.ResponseType = "id_token token"; options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("email"); options.Scope.Add("movieAPI"); options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; // 映射标准邮箱Claim类型 options.ClaimActions.MapUniqueJsonKey(JwtClaimTypes.Email, "email"); options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { NameClaimType = JwtClaimTypes.GivenName, // 避免Claim类型被自动转换 ValidateIssuer = true }; });
4. 确认IdentityServer客户端配置完整性
检查Config.cs中的客户端配置,确保AllowedScopes包含email,且未限制Claim类型:
// 客户端配置 AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Email, "movieAPI", }, Enabled = true
- 重启IdentityServer和MVC客户端
- 重新登录,查看IdentityServer的
OnUserInformationReceived日志,确认邮箱Claim存在 - 在MVC客户端Razor页面重新遍历
User.Claims,检查是否包含email类型的Claim
内容的提问来源于stack exchange,提问作者Bryan Dellinger

