使用新的基于AAD的访问控制功能通过网络浏览器通过URI访问Blob文件

时间:2019-03-26 08:25:56

标签: azure azure-active-directory botframework azure-storage azure-functions

有了Azure Storage support for Azure Active Directory based access control的公告,是否可以仅通过URI来通过Web浏览器提供Blob(特定文件)?

我要简化的用例是使一些人可以访问blob上的文件,而不必将SAS令牌附加到URI。相反,当尝试在其Web浏览器中打开普通URI时启动典型的OAuth流程将是很不错的选择。

就我而言,我们希望授予用户通过基于Microsoft Bot框架构建的支持bot上载到blob存储的文件的权限。支持人员应该可以在他们选择的Web浏览器中访问我们支持系统中的链接。

此公告支持的该用例,还是仅适用于编码的OAuth流,这意味着我们仍然必须实现一些代码?

如果是这样,是否有一个很好的示例说明如何从Azure Function应用启动OAuth流程并使用生成的令牌下载文件(通过Azure Storage REST终结点)?

2 个答案:

答案 0 :(得分:1)

如果要对存储使用基于Azure Active Directory的访问控制,则需要获取的是访问令牌。以下是供您参考的步骤。

  1. Register an application

2。Assign a build-in RBAC role to this application。这取决于您要分配给应用程序的角色。 enter image description here

3。Get the access tokenenter image description here

4。使用访问令牌,现在您可以调用storage rest api。 enter image description here

答案 1 :(得分:1)

尽管this answer在技术上是正确的,但这并不是对我最初的问题的直接回答。

我一直在寻找一种向业务用户提供任何Blob的直接uri的方法,因此他们可以简单地在任何Web浏览器中打开它并查看文件。

就我而言,我们希望授予用户通过基于Microsoft Bot框架构建的支持bot上载到blob存储的文件的权限。例如。将附件作为我们支持系统中的链接,以供支持代理访问。

深入研究之后,我可以回答自己的问题:

  

随着宣布Azure存储支持基于Azure Active Directory的访问控制,是否可以仅通过URI通过Web浏览器提供Blob(特定文件)?

,这是不可能的。更具体地说,仅将直接uri打开到浏览器中的blob不会触发OAuth流。相反,除非您提供SAS查询令牌或将Blob设置为public,否则它将始终为您提供ResourceNotFound响应。从安全角度来看(当涉及普通用户时)这两种解决方案都是不好的,而UX显然很差。

解决方案

寻找一种完全存档我想要的方法的方法,我想到了通过使用路由模板传递构造路径的uri的fileName来为所有业务用户提供附件的Azure函数的想法。

无论如何考虑安全性和访问令牌的需求,您都可以通过平台身份验证(也称为easyAuth)来保护功能应用程序。

但是,这还不够,并且无法直接配置解决方案的所有部分。这就是为什么我要分享它。

TL; DR高级步骤:

  1. 创建一个新的Function App(建议使用v2)
  2. authentication(easyAuth)启用功能App
  3. 为功能应用程序创建服务主体(也称为应用程序注册)(在步骤2中隐含)
  4. 在应用程序注册上添加其他允许的令牌访问者https://storage.microsoft.com
  5. 编辑应用程序注册清单以包括Azure Storage API权限(请参阅下面的特殊说明)
  6. 在Azure资源浏览器中修改authSettings以包括additionalLoginParams用于令牌响应和resourceId
  7. 至少将blob上的Storage Blob Data Reader权限授予所有访问文件的用户
  8. 部署功能应用程序,调用它,访问用户令牌,调用blob存储并将结果呈现给用户(请参见下面的代码示例)

关于Azure存储API权限和访问令牌的注释(步骤5和6)

如最新的documentation中所述,Azure存储上的AAD身份验证支持,该应用必须具有user_impersonation的resourceId https://storage.azure.com/权限范围。不幸的是,文档未说明如何设置此API权限,因为它在门户中不可见(至少我没有找到它)。

因此,唯一的方法是通过直接在azure门户中编辑应用程序注册清单,通过其全局GUID(可在Internet上找到)进行设置。

更新事实证明,在门户网站中找不到正确的权限是一个错误。请参阅我的答案here。手动修改清单会产生相同的结果,但是直接在门户网站中进行修改会更加方便。

Manifest

"requiredResourceAccess": [
    {
        "resourceAppId": "e406a681-f3d4-42a8-90b6-c2b029497af1",
        "resourceAccess": [
            {
                "id": "03e0da56-190b-40ad-a80c-ea378c433f7f",
                "type": "Scope"
            }
        ]
    },
    {
        "resourceAppId": "00000002-0000-0000-c000-000000000000",
        "resourceAccess": [
            {
                "id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
                "type": "Scope"
            }
        ]
    }
]

第一个是Azure存储上的user_impersonation范围,第二个是User.Read的图形权限,在大多数情况下这是有帮助或需要的。

上传修改后的清单后,您可以在应用注册的 API权限标签上对其进行验证。

由于easyAuth使用的是AAD的v1端点,因此您的应用需要在触发OAuth流时通过传递resource=https://storage.azure.com/来静态地请求这些权限。

此外,Azure存储需要用于身份验证标头的承载架构,因此需要JWT令牌。要从端点获取JWT令牌,我们需要传递response_type=code id_token作为附加登录参数。

两者都只能通过Azure Resource explorer或powershell来完成。

使用Azure资源浏览器,您必须一直导航到功能应用程序上的authSettings并相应地设置additionalLoginParams

enter image description here

"additionalLoginParams": [
  "response_type=code id_token",
  "resource=https://storage.azure.com/"
]

enter image description here

代码示例

这是使用上述所有机制的简单Azure功能的完整代码示例。

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;

namespace Controller.Api.v1.Org
{
    public static class GetAttachment
    {
        private const string defaultContentType = "application/octet-stream";

        [FunctionName("GetAttachment")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "v1/attachments")] HttpRequest req,
            ILogger log)    
        {
            if (!req.Query.ContainsKey("fileName"))
                return new BadRequestResult();

            // Set the file name from query parameter
            string fileName = req.Query["fileName"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);

            fileName = fileName ?? data?.name;

            // Construct the final uri. In this sample we have a applicaiton setting BLOB_URL
            // set on the function app to store the target blob
            var blobUri = Environment.GetEnvironmentVariable("BLOB_URL") + $"/{fileName}";

            // The access token is provided as this special header by easyAuth.
            var accessToken = req.Headers.FirstOrDefault(p => p.Key.Equals("x-ms-token-aad-access-token", StringComparison.OrdinalIgnoreCase));

            // Construct the call against azure storage and pass the user token we got from easyAuth as bearer
            using (var client = new HttpClient())
            {
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Value.FirstOrDefault());
                client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
                client.DefaultRequestHeaders.Add("Accept", "*/*");
                client.DefaultRequestHeaders.Add("x-ms-version", "2017-11-09");

                // Serve the response directly in users browser. This code works against any browser, e.g. chrome, edge or even internet explorer
                var response = await client.GetAsync(blobUri);
                var contentType = response.Content.Headers.FirstOrDefault(p => p.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase));
                var byteArray = await response.Content.ReadAsByteArrayAsync();

                var result = new FileContentResult(byteArray, contentType.Value.Any() ? contentType.Value.First() : defaultContentType);

                return result;
            }
        }
    }
}