簡體   English   中英

使用托管標識在 Azure 中進行應用服務到應用服務的身份驗證

[英]App service to app service auth in Azure using Managed Identity

我在 Azure 中設置了兩個應用服務。 'Parent' 和 'Child' 都公開 API 端點。

  • 子節點具有端點“Get”。
  • Parent 有端點“Get”和“GetChild”(使用 HttpClient 在 Child 上調用“Get”)。

我希望所有子端點都需要通過托管身份和 AAD 進行身份驗證,並且我希望所有父端點都允許匿名。 但是在 Azure 中,我想將父應用服務設置為有權調用子應用服務。 因此,子端點只能通過使用父端點來訪問(或者如果您對用戶帳戶具有直接使用子端點的權限)。

在 Azure 門戶中:

認證/授權

  • 我在兩個應用服務上都啟用了“應用服務身份驗證”。
  • 孩子設置為“使用 AAD 登錄”。
  • 父級設置為“允許匿名請求”。
  • 兩者都在“身份驗證提供程序”下配置了 AAD。

身份

  • 將兩個應用服務都設置為“開啟”

訪問控制 (IAM)

  • 子級將父級作為角色分配,類型 =“應用服務或功能應用”,角色 =“貢獻者”

通過以上所有設置:

  • 調用 Child -> Get,需要我登錄
  • 調用Parent -> Get,返回預期響應200 OK
  • 調用Parent -> GetChild,返回“401 - 您無權查看此目錄或頁面”

不使用客戶端 ID/秘密/密鑰/等,因為我認為托管身份背后的想法是將所有這些都扔到窗外,鑒於上述所有內容,Parent 應該能夠調用 Child 嗎? 如果是這樣,我設置錯了什么?

  • 調用Parent -> GetChild,返回“401 - 您無權查看此目錄或頁面”

不使用客戶端 ID/秘密/密鑰/等,因為我認為托管身份背后的想法是將所有內容都扔出窗外,鑒於上述所有內容,父級是否應該能夠調用子級? 如果是這樣,我設置錯了什么?

我注意到當前設置有兩件事。

1. 使用托管身份獲取令牌以從“父”調用“子”服務端點

Managed Identity 只為您的應用服務提供一個身份(沒有管理/維護應用程序機密或密鑰的麻煩)。 然后可以使用此標識獲取不同 Azure 資源的令牌。

但是使用這個身份並獲取相關資源的令牌仍然是您的應用程序的責任。 在這種情況下,相關資源將是您的“子”API。 我認為這可能是您現在缺少的部分。

Microsoft Docs 上的相關文檔 - 如何為應用服務和 Azure 函數使用托管標識 > 獲取 Azure 資源的令牌

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://<yourappservicename>.azurewebsites.net ,但您可以通過轉到 Azure AD 應用程序,然后找到相關的應用程序注冊 > 清單來找到它的值。 您還可以使用目標應用程序(即“Child”)的 applicationId 來獲取令牌。

如果您不使用 C#/.NET ,上面相同的 Microsoft Docs 鏈接也提供了有關如何使用來自任何平台的托管標識和基於 REST 的調用獲取令牌的指南。 使用 REST 協議

這是一篇博客文章,也提供了一個很好的演練 - 使用托管服務標識 (MSI) 調用受 Azure AD 保護的網站

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

我看到您已從 IAM 將參與者角色分配給父應用服務的身份。 此角色分配適用於 Azure RBAC 並有助於授予管理資源的權限,但 Azure AD 角色聲明的工作方式不同。

如果您想要做的是為父應用程序分配一個角色,可以在子應用程序中檢查該角色,然后才允許調用,則有一種不同的設置方式。

我首先要提到的是,這種基於角色的設置適用於一些高級場景,並不是真正必須這樣做。 按照上述第 1 點中的步驟操作后,您應該能夠從“家長”調用“孩子”服務。

現在,一旦從 Parent 到 Child 的調用開始工作,您可能希望將對 Child 應用程序服務的訪問權限限制為僅“Parent”或一些有效的應用程序。 這是實現這一目標的兩種方法。

這兩種方法都在 Microsoft Docs 上進行了解釋 - Microsoft 身份平台和 OAuth 2.0 客戶端憑據流

關聯 SO 帖子和博客

方法 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”的角色,否則您可以拒絕未授權異常的調用。

擴展已接受的答案。

  1. 您需要在目標應用注冊的清單中定義“應用角色”。 這是用於表示資源(API 應用服務)的應用注冊。

  2. 然后使用 Azure CLI 向企業應用程序(為客戶端應用程序設置托管標識時生成的)授予對該“應用程序角色”的權限。 有關詳細步驟,請參閱本文中的“API 和其他 Azure AD 注冊應用程序” https://blog.yannickreekmans.be/secretless-applications-add-permissions-to-a-managed-identity/

授予權限后,您可以使用以下方法檢索令牌。 下面的代碼片段使用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;
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM