在 Azure 应用服务身份验证中使用用户标识

本文演示如何在使用应用服务中的内置身份验证和授权时使用用户标识。

在应用代码中访问用户声明

对于所有语言框架,应用服务都通过将传入令牌(无论此令牌是来自经过身份验证的最终用户,还是来自客户端应用程序)中的声明注入请求头,使其可供代码使用。 不允许外部请求设置这些标头,因此,只会提供应用服务设置的标头。 部分标头示例如下:

标头 说明
X-MS-CLIENT-PRINCIPAL 可用声明的 Base64 编码 JSON 表示形式。 有关详细信息,请参阅解码客户端主体标头
X-MS-CLIENT-PRINCIPAL-ID 由标识提供者设置的调用方标识符。
X-MS-CLIENT-PRINCIPAL-NAME 由标识提供者设置的调用方易读名称,例如电子邮件地址、用户主体名称。
X-MS-CLIENT-PRINCIPAL-IDP 应用服务身份验证使用的标识提供者的名称。

提供者令牌也通过类似的标头公开。 例如,Microsoft 标识提供者还会根据需要设置 X-MS-TOKEN-AAD-ACCESS-TOKENX-MS-TOKEN-AAD-ID-TOKEN

注意

不同的语言框架可能会以不同格式(例如小写或标题字首大写)向应用代码显示这些标头。

使用任何语言或框架编写的代码均可从这些标头获取所需信息。 解码客户端主体标头介绍了此过程。 对于某些框架,该平台还提供了其他可能更方便的选项。

解码客户端主体标头

X-MS-CLIENT-PRINCIPAL 包含 Base64 编码 JSON 形式的完整可用声明集。 这些声明会经历默认的声明映射过程,因此某些声明的名称可能与直接处理令牌时看到的名称不同。 解码有效负载的结构如下所示:

{
    "auth_typ": "",
    "claims": [
        {
            "typ": "",
            "val": ""
        }
    ],
    "name_typ": "",
    "role_typ": ""
}
properties 类型 说明
auth_typ 字符串 应用服务身份验证使用的标识提供者的名称。
claims 对象数组 表示可用声明的对象数组。 每个对象包含 typval 属性。
typ 字符串 声明名称。 这可能受默认声明映射的约束,并且可能与令牌中包含的相应声明不同。
val 字符串 声明的值。
name_typ 字符串 名称声明类型:通常是提供 name 声明(如果已定义)的相关方案信息的 URI。
role_typ 字符串 角色声明类型:通常是提供 role 声明(如果已定义)的相关方案信息的 URI。

若要处理此标头,应用需要解码有效负载并循环访问 claims 数组以查找感兴趣的声明。 将它们转换为应用的语言框架使用的表示形式可能会比较方便。 下面以 C# 为例,介绍了构造供应用使用的 ClaimsPrincipal 类型的过程:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http;

public static class ClaimsPrincipalParser
{
    private class ClientPrincipalClaim
    {
        [JsonPropertyName("typ")]
        public string Type { get; set; }
        [JsonPropertyName("val")]
        public string Value { get; set; }
    }

    private class ClientPrincipal
    {
        [JsonPropertyName("auth_typ")]
        public string IdentityProvider { get; set; }
        [JsonPropertyName("name_typ")]
        public string NameClaimType { get; set; }
        [JsonPropertyName("role_typ")]
        public string RoleClaimType { get; set; }
        [JsonPropertyName("claims")]
        public IEnumerable<ClientPrincipalClaim> Claims { get; set; }
    }

    public static ClaimsPrincipal Parse(HttpRequest req)
    {
        var principal = new ClientPrincipal();

        if (req.Headers.TryGetValue("x-ms-client-principal", out var header))
        {
            var data = header[0];
            var decoded = Convert.FromBase64String(data);
            var json = Encoding.UTF8.GetString(decoded);
            principal = JsonSerializer.Deserialize<ClientPrincipal>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
        }

        /** 
         *  At this point, the code can iterate through `principal.Claims` to
         *  check claims as part of validation. Alternatively, we can convert
         *  it into a standard object with which to perform those checks later
         *  in the request pipeline. That object can also be leveraged for 
         *  associating user data, etc. The rest of this function performs such
         *  a conversion to create a `ClaimsPrincipal` as might be used in 
         *  other .NET code.
         */

        var identity = new ClaimsIdentity(principal.IdentityProvider, principal.NameClaimType, principal.RoleClaimType);
        identity.AddClaims(principal.Claims.Select(c => new Claim(c.Type, c.Value)));
        
        return new ClaimsPrincipal(identity);
    }
}

特定于框架的替代方案

对于 ASP.NET 4.6 应用,应用服务会在 ClaimsPrincipal.Current 中填充经过身份验证的用户声明,使你能够遵循标准的 .NET 代码模式(包括 [Authorize] 属性)。 同样,对于 PHP 应用,应用服务会填充 _SERVER['REMOTE_USER'] 变量。 对于 Java 应用,可从 Tomcat servlet 访问声明。

对于 Azure Functions,没有为 .NET 代码填充 ClaimsPrincipal.Current,但你仍然可以在请求头中找到用户声明,也可通过请求上下文,甚至通过绑定参数来获取 ClaimsPrincipal 对象。 有关详细信息,请参阅在 Azure Functions 中使用客户端标识

对于 .NET Core,Microsoft.Identity.Web 支持使用应用服务身份验证填充当前用户。 若要了解详细信息,可以在 Microsoft.Identity.Web Wiki 上阅读相关内容,或查看本教程中有关访问 Microsoft Graph 的 Web 应用的演示

注意

若要使声明映射正常工作,必须启用令牌存储

使用 API 访问用户声明

如果已为应用启用令牌存储,你还可调用 /.auth/me 来获得经过身份验证的用户的其他详细信息。