使用托管标识在 Azure 中应用服务到应用服务身份验证

Posted

技术标签:

【中文标题】使用托管标识在 Azure 中应用服务到应用服务身份验证【英文标题】:App service to app service auth in Azure using Managed Identity 【发布时间】:2019-10-26 02:50:29 【问题描述】:

我在 Azure 中设置了两个应用服务。 'Parent' 和 'Child' 都暴露 API 端点。

孩子有端点“Get”。 Parent 具有端点“Get”和“GetChild”(使用 HttpClient 在 Child 上调用“Get”)。

我希望所有子端点都要求通过托管身份和 AAD 进行身份验证,并且我希望所有父端点都允许匿名。但是在 Azure 中,我想将父应用服务设置为有权调用子应用服务。因此,子端点只能通过使用父端点访问(或者如果您对用户帐户具有直接使用子节点的权限)。

在 Azure 门户中:

身份验证/授权

我已在两个应用服务上启用了“应用服务身份验证”。 孩子设置为“使用 AAD 登录”。 父级设置为“允许匿名请求”。 两者都在“身份验证提供程序”下配置了 AAD。

身份

两个应用服务都设置为“开启”

访问控制 (IAM)

子级将父级作为角色分配,类型 =“应用服务或功能应用”,角色 =“贡献者”

以上所有设置:

Calling Child -> Get,需要我登录 调用Parent -> Get,返回200 OK的预期响应 调用Parent -> GetChild,返回“401 - 您无权查看此目录或页面”

如果不使用客户端 ids/Secrets/Keys/etc,因为我认为托管身份背后的想法是将所有这些都扔到窗外,鉴于上述所有情况,Parent 是否应该能够调用 Child?如果是这样,我的设置有什么问题?

【问题讨论】:

【参考方案1】:

扩展已接受的答案。

    您需要在目标应用注册的清单中定义一个“应用角色”。这是用于表示资源(API App Service)的应用注册。

    然后,您使用 Azure CLI 将该“应用程序角色”的权限授予企业应用程序(当您为客户端应用程序设置托管标识时生成的权限)。详细步骤见本文“API 和其他 Azure AD 注册应用”https://blog.yannickreekmans.be/secretless-applications-add-permissions-to-a-managed-identity/

授予权限后,您可以使用以下方法检索令牌。下面的代码 sn-p 使用 Azure.Identity,它现在是 Azure 中托管标识的推荐库。

public class AzureAdTokenRetriever : IAzureAdTokenRetriever

    private readonly ILogger<AzureAdTokenRetriever> logger;
    private readonly IMemoryCache inMemoryCache;

    public AzureAdTokenRetriever(
        ILogger<AzureAdTokenRetriever> logger,
        IMemoryCache inMemoryCache)
    
        this.logger = logger;
        this.inMemoryCache = inMemoryCache;
    

    public async Task<string> GetTokenAsync(string resourceId, string scope = "/.default")
    
        var resourceIdentifier = resourceId + scope;
        if (inMemoryCache.TryGetValue(resourceIdentifier, out var token))
        
            this.logger.LogDebug("Token for ResourceId and Scope were fetched from cache", resourceId, scope);
            return (string)token;
        

        var tokenCredential = new DefaultAzureCredential();
        var accessToken = await tokenCredential.GetTokenAsync(
            new TokenRequestContext(new []  resourceIdentifier ), CancellationToken.None)
            .ConfigureAwait(false);

        // Set cache options with expiration 5 minutes before the token expires
        var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(accessToken.ExpiresOn.AddMinutes(-5));
        inMemoryCache.Set(resourceIdentifier, accessToken.Token, cacheEntryOptions);
        this.logger.LogDebug("Token for ResourceId and Scope saved in cache with expiration of TokenExpiry",
            resourceId, scope, cacheEntryOptions.AbsoluteExpiration);

        return accessToken.Token;
    

【讨论】:

【参考方案2】:
调用Parent -> GetChild,返回“401 - 您无权查看此目录或页面”

不使用 Client ids/Secrets/Keys/etc,就像我想的那样 托管身份的背后是将所有这些都扔到窗外,给定 以上所有,父母应该可以打电话给孩子吗?如果是这样,什么 我是不是设置错了?

我注意到当前设置有两点。

1.使用托管身份获取令牌以从“父”调用“子”服务端点

托管身份仅为您的应用服务提供身份(无需管理/维护应用机密或密钥)。然后,此身份可用于获取不同 Azure 资源的令牌。

但您的应用仍有责任使用此身份并获取相关资源的令牌。在这种情况下,相关资源将是您的“子”API。我想这可能是你现在缺少的部分。

Microsoft Docs 上的相关文档 - How to use managed identities for App Service and Azure Functions > Obtain tokens for Azure resources

using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Azure.KeyVault;
// ...
var azureServiceTokenProvider = new AzureServiceTokenProvider();
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://vault.azure.net");

// change this to use identifierUri for your child app service. 
// I have used the default value but in case you've used a different value, find it by going to Azure AD applications > your app registration > manifest
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://<yourchildappservice>.azurewebsites.net");

此 C#/.NET 示例使用 Microsoft.Azure.Services.AppAuthentication nuget 包并获取 Azure Key Vault 的令牌。在您的情况下,您将用您的“子”服务的 identifierUri 替换 https://vault.azure.net。它通常默认设置为https://&lt;yourappservicename&gt;.azurewebsites.net,但您可以通过转到 Azure AD 应用程序然后找到相关的应用注册 > 清单来找到它的值。您还可以将 applicationId 用于目标应用程序(即“Child”)来获取令牌。

如果您不使用 C#/.NET,上面相同的 Microsoft Docs 链接还提供了有关如何使用托管标识和 基于 REST 的调用获取令牌的指南任何平台。 Using REST Protocol

这是一篇博文,也提供了很好的介绍 - Call Azure AD protected website using Managed Service Identity (MSI)

2。 Azure RBAC 角色分配不同于您可能想要使用的 Azure AD 角色

我看到您已从 IAM 将参与者角色分配给父应用服务的身份。此角色分配适用于 Azure RBAC,并有助于授予管理资源的权限,但 Azure AD 角色声明的工作方式不同。

如果您要做的是为父应用分配一个角色,可以在子应用中进行检查,然后才允许调用,则有另一种设置方式。

我首先要提到的是,这种基于角色的设置是针对一些高级场景的,并不是必须要做的。按照上述第 1 点中的步骤操作后,您应该可以从“父母”调用“儿童”服务。

现在,一旦从父级到子级的调用开始工作,您可能希望将对子级应用服务的访问限制为仅“父级”或少数有效应用程序。以下是实现这一目标的两种方法。

这两种方法都在此处的 Microsoft Docs 中进行了解释 - Microsoft identity platform and the OAuth 2.0 client credentials flow

关联 SO 帖子和博客

Is there a way to secure an Azure Function that will only be called from a specific Azure Logic App?

Azure Active Directory - How to restrict Backend API App Registration to a specific client App Registration

https://joonasw.net/view/calling-your-apis-with-aad-msi-using-app-permissions

方法 1 - 使用访问控制列表

当您的“子”API 收到令牌时,它可以解码该令牌并从 appidiss 声明中提取客户端的应用程序 ID。然后它将应用程序与它维护的访问控制列表 (ACL) 进行比较。

根据您的要求,API 可能仅向特定客户端授予完整权限的子集或所有权限。

方法 2 - 使用应用程序权限或角色

配置您的子 API 应用程序以公开一组应用程序权限(或角色)。

这种方法更具声明性,因为您定义了一个应用程序权限,该权限需要分配给任何可以调用您的child-api 的应用程序。

导航到 Azure Active Directory > 应用注册 > child-api 应用的应用注册 > 清单

添加一个新的应用程序角色.. 像这样使用 json:

"appRoles": [

  "allowedMemberTypes": [
    "Application"
  ],
  "displayName": "Can invoke my API",
  "id": "fc803414-3c61-4ebc-a5e5-cd1675c14bbb",
  "isEnabled": true,
  "description": "Apps that have this role have the ability to invoke my child API",
  "value": "MyAPIValidClient"
]

将应用权限分配给您的前端应用

New-AzureADServiceAppRoleAssignment -ObjectId <parentApp.ObjectId> -PrincipalId <parentApp.ObjectId> -Id "fc803414-3c61-4ebc-a5e5-cd1675c14bbb" -ResourceId <childApp.ObjectId>

现在,在您的子 api 收到的身份验证令牌中,您可以检查角色声明集合是否必须包含名为“MyAPIValidClient”的角色,否则您可以拒绝未授权异常的调用。

【讨论】:

感谢 Rohit 提供如此全面的答案,我已经实现了您的代码 sn-p(我正在使用 C#.NET),并且收到了一个令牌。我将该令​​牌作为 Bearer 令牌添加到 Child -> Get 的 HttpClient 请求中,但我仍然收到 401。我从头开始重新创建了我的应用程序服务,以确保我没有无意中设置任何设置。你还有什么可以给的吗? 我实际上已经设法通过使用 ApplicationId (ClientId) 代替 identifierUri 来让它工作。虽然我有一个端到端的工作很棒,但如果您有任何建议,我更喜欢更具人类可读性的东西? @user10238915 不客气。关于获取令牌,理想情况下,您应该能够做到这一点,即 applicationId 或 identifierUri .. 我不确定为什么只有 applicationId 对您有用。我可以建议的一件事是确保 identifierUri 出现在允许的令牌受众中。请参阅我在答案中已经共享的链接的第 4 步。blogs.msdn.microsoft.com/waws/2018/10/23/… 第一个链接中的锚点错误 - 它应该以“#obtain-”而不是“#obtaining-”开头。 (我建议修改,但被拒绝)。 有道理@BillH,不知道为什么编辑被拒绝,但看起来你是正确的,自从我最初发布答案以来,MS Docs 上的链接已经改变。谢谢你的信息,我会更新的。

以上是关于使用托管标识在 Azure 中应用服务到应用服务身份验证的主要内容,如果未能解决你的问题,请参考以下文章

使用托管标识从 Azure Logic 应用向 Azure Function 进行身份验证

使用 Azure 托管服务标识和应用服务身份验证的服务到服务身份验证?

如何使用服务托管标识在 Azure 中使用 Terraform 预配资源

为啥我的 Azure 应用服务无法使用托管标识连接到 Azure 存储帐户?

在 Azure 中整合托管标识与身份验证/授权标识

使用托管标识与 Azure SQL 的 EF Core 连接