You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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
验证步骤
  1. 重启IdentityServer和MVC客户端
  2. 重新登录,查看IdentityServer的OnUserInformationReceived日志,确认邮箱Claim存在
  3. 在MVC客户端Razor页面重新遍历User.Claims,检查是否包含email类型的Claim

内容的提问来源于stack exchange,提问作者Bryan Dellinger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 02:24:56