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

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

先决条件

已启用应用服务身份验证/授权模块的 Azure 应用服务上运行的 Web 应用程序。

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

应用的经过身份验证的最终用户或客户端应用程序在传入令牌中发出声明。 应用服务通过将声明注入请求标头,使这些声明可供代码使用。 不允许外部请求设置这些标头,因此仅在应用服务设置它们时才存在。

可以使用应用服务身份验证提供的声明信息在应用代码中执行授权检查。 任何语言或框架中的代码都可以从请求标头获取所需的信息。 某些代码框架提供了可能更方便的额外选项。 请参阅 特定于框架的替代方法

下表介绍了一些示例标头:

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

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

注意

应用服务使请求标头可用于所有语言框架。 不同的语言框架可能会以不同格式(例如小写或词首字母大写)向应用代码显示这些标头。

解码客户端主体标头

标头 X-MS-CLIENT-PRINCIPAL 包含 Base64 编码 JSON 中的完整可用声明集。 若要处理此标头,应用必须解码有效负载并循环访问 claims 数组以查找相关的声明。

注意

这些声明将经历默认声明映射过程,因此某些名称可能与令牌中显示的名称不同。

解码的有效负载结构如下所示:

{
    "auth_typ": "",
    "claims": [
        {
            "typ": "",
            "val": ""
        }
    ],
    "name_typ": "",
    "role_typ": ""
}

下表描述了这些属性。

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

为方便起见,可以将声明转换为应用语言框架使用的表示形式。 以下 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 });
        }

此时,代码可以循环访问 principal.Claims 以检查声明作为验证的一部分。 或者,可以转换为 principal.Claims 标准对象,并在请求管道中稍后使用它来执行这些检查。 还可以使用该对象来关联用户数据和其他用途。

函数的其余部分执行此转换,以创建一个可供其他 .NET 代码使用的 ClaimsPrincipal

        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);
    }
}

特定于框架的替代方案

注意

为使声明映射功能正常工作,您必须为应用程序启用令牌存储

使用 API 访问用户声明

如果为应用启用了 令牌存储 ,还可以调用 /.auth/me 以获取有关经过身份验证的用户的其他详细信息。