【问题标题】:Get the user's email address from Azure AD via OpenID Connect通过 OpenID Connect 从 Azure AD 获取用户的电子邮件地址
【发布时间】:2015-06-22 15:16:50
【问题描述】:

我正在尝试使用他们的 Office 365 帐户对我的站点的用户进行身份验证,因此我一直遵循有关使用 OWIN OpenID Connect 中间件添加身份验证的指南,并成功地验证并检索了他们的个人资料。

我现在正在尝试获取用户的电子邮件地址(以便我可以使用他们的联系方式填充他们的系统帐户),但我似乎无法收到电子邮件声明。我尝试使用范围 openid profile email 发出请求,但声明集不包含任何邮件信息。

有没有办法通过 OpenID Connect 端点从 Azure AD 获取用户的电子邮件?

【问题讨论】:

  • FWIW:连接到我的 Live ID 的 Azure AD 帐户返回“邮件”声明,无论请求的 scope 是什么,所以我猜这是服务器端配置(或限制)。跨度>
  • @HansZ。您能否分享您的客户端应用程序已授予的权限?我的只有启用登录和读取用户配置文件
  • 我的完全一样:只有Enable sign-on and read users' profiles启用,在permissions to other applications部分,用于条目Windows Azure Active Directory
  • 您找到解决方案了吗?我看到了完全相同的问题。

标签: c# owin azure-active-directory openid-connect


【解决方案1】:

在找到解决方案之前,我在同一个问题上苦苦挣扎了几天。回答您的问题:是的,只要您:

  1. 在您的请求中包含 profileemail 范围,并且
  2. 在 Azure 门户 Active Directory 部分中配置您的应用程序,以在 委派权限 下包含登录和读取用户配置文件

请注意,电子邮件地址可能不会在 email 声明中返回:在我的情况下(一旦我让它工作)它会在 name 声明中返回。

但是,根本没有得到电子邮件地址可能是由以下问题之一引起的:

没有与 Azure AD 帐户关联的电子邮件地址

根据Scopes, permissions, and consent in the Azure Active Directory v2.0 endpoint 的本指南,即使您包含email 范围,您也可能无法回复电子邮件地址:

仅当电子邮件地址与用户帐户相关联时,email 声明才会包含在令牌中,但情况并非总是如此。如果它使用email 范围,您的应用应该准备好处理令牌中不存在email 声明的情况。

如果您收到其他与个人资料相关的声明(例如 given_namefamily_name),这可能就是问题所在。

中间件丢弃的声明

这就是我的原因。我没有收到任何与个人资料相关的声明(名字、姓氏、用户名、电子邮件等)。

在我的例子中,身份处理堆栈如下所示:

问题出在 IdentityServer3.AspNetIdentity AspNetIdentityUserService 类中:InstantiateNewUserFromExternalProviderAsync() method looks like this

protected virtual Task<TUser> InstantiateNewUserFromExternalProviderAsync(
    string provider,
    string providerId,
    IEnumerable<Claim> claims)
{
    var user = new TUser() { UserName = Guid.NewGuid().ToString("N") };
    return Task.FromResult(user);
}

注意它会传入一个声明集合然后忽略它。我的解决方案是创建一个派生自此的类并将该方法重写为如下所示:

protected override Task<TUser> InstantiateNewUserFromExternalProviderAsync(
    string provider,
    string providerId,
    IEnumerable<Claim> claims)
{
    var user = new TUser
    {
        UserName = Guid.NewGuid().ToString("N"),
        Claims = claims
    };
    return Task.FromResult(user);
}

我不确切知道您正在使用什么中间件组件,但很容易看到从您的外部提供商返回的原始声明;这至少会告诉您他们恢复正常,并且问题出在您的中间件的某个地方。只需将 Notifications 属性添加到您的 OpenIdConnectAuthenticationOptions 对象,如下所示:

// Configure Azure AD as a provider
var azureAdOptions = new OpenIdConnectAuthenticationOptions
{
    AuthenticationType = Constants.Azure.AuthenticationType,
    Caption = Resources.AzureSignInCaption,
    Scope = Constants.Azure.Scopes,
    ClientId = Config.Azure.ClientId,
    Authority = Constants.Azure.AuthenticationRootUri,
    PostLogoutRedirectUri = Config.Identity.RedirectUri,
    RedirectUri = Config.Azure.PostSignInRedirectUri,
    AuthenticationMode = AuthenticationMode.Passive,
    TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false
    },
    Notifications = new OpenIdConnectAuthenticationNotifications
    {
        AuthorizationCodeReceived = context =>
        {
            // Log all the claims returned by Azure AD
            var claims = context.AuthenticationTicket.Identity.Claims;
            foreach (var claim in claims)
            {
                Log.Debug("{0} = {1}", claim.Type, claim.Value);
            }
            return null;
        }
    },
    SignInAsAuthenticationType = signInAsType // this MUST come after TokenValidationParameters
};

app.UseOpenIdConnectAuthentication(azureAdOptions);

另见

【讨论】:

  • 我也在使用 IdentityServer(4),但我认为这不是问题;来自 AzureAD 的 id_token 不包括与配置文件相关的声明,即使在 IdentityServer 处理它之前也是如此。我指定openid profile email 作为范围。 AAD 用户有一个关联的电子邮件。并不是说有包含电子邮件的 preferred_username 声明,但使用它似乎很脆弱。
【解决方案2】:

几天来,我一直在为同样的问题苦苦挣扎……我从拥有个人 Microsoft 帐户的用户那里获得了电子邮件地址,但对于拥有公司 Microsoft 帐户的用户却没有。

对于个人帐户,电子邮件地址会在 email 字段中返回,正如人们所期望的那样。

对于公司帐户,电子邮件地址在 preferred_username 字段中返回。

请放心,我还没有发现其他 Microsoft 变体...

【讨论】:

    【解决方案3】:

    您是否可以选择将登录请求中的 &resource=https://graph.windows.net 传递到授权端点,然后查询 Azure AD Graph API 以获取经过身份验证的组织用户的 Office 365 电子邮件地址?例如,获取https://graph.windows.net/me/mail?api-version=1.5

    有关其他参考,请参阅 AzureADSamples GitHub 上的 WebApp-WebAPI-MultiTenant-OpenIdConnect-DotNet 代码示例。

    【讨论】:

    • 这实际上并没有通过 OpenID Connect 令牌传递邮件声明,但我很欣赏这是一个可行的解决方法。
    【解决方案4】:

    2019 年的更新答案:email 声明是一个可选声明,可能未包含在请求中 (Source)

    对于托管用户(租户内的用户),必须通过此可选声明请求,或者仅在 v2.0 上,使用 OpenID 范围请求。

    您必须更新 Azure 门户中的清单文件以包含可选声明,如下所示:

    "optionalClaims": {
        "idToken": [
            {
                "name": "email",
                "source": null,
                "essential": false,
                "additionalProperties": []
            }
        ],
    }
    

    这个答案的部分灵感来自this blog post

    【讨论】:

    • 这对我来说是一个很好的线索。虽然没有提供完整的答案,但我能够将我的问题追溯到 Azure AD,Azure 门户中的应用程序注册,我在其中添加了电子邮件作为可选参数。
    • @GlennMoseley 您介意对此进行扩展吗?您采取了哪些措施来解决电子邮件问题?我现在正在面对它
    • @Andrey 嗨,安德烈,抱歉已经有一段时间了。我怀疑你仍然面临这个问题,但我会添加我的解决方案,以防万一,或者任何进一步的跟进。在您配置的 AD 租户的 Azure 门户中。如果您转到应用注册,请选择您要配置的应用名称,然后选择令牌配置。您可以添加电子邮件的可选参数。这需要同时配置 OpenID 连接范围。
    【解决方案5】:

    在同样的情况下,我最终得到了一个非常简单的代码,它返回所有用户在登录后可以访问的声明(包括电子邮件)

    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using Microsoft.AspNetCore.Mvc;
    
    namespace Controllers
    {
        public class BaseController : Controller
        {
            protected string GetCurrentUserIDFromClaims()
            {
                return User.FindFirstValue(ClaimTypes.NameIdentifier);
            }
    
            protected List<string> AllClaimsFromAzure()
            {
                ClaimsIdentity claimsIdentity = ((ClaimsIdentity)User.Identity);
                return claimsIdentity.Claims.Select(x => x.Value).ToList();
            }
    
            protected string GetCurrentEmailFromAzureClaims()
            {
                return AllClaimsFromAzure()[3];
            }
        }
    }
    

    【讨论】:

      【解决方案6】:

      从用户的声明中获取upn 值:

      var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
      string email = userClaims?.FindFirst(System.Security.Claims.ClaimTypes.Upn)?.Value;
      

      【讨论】:

        猜你喜欢
        • 2015-11-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-23
        相关资源
        最近更新 更多