代码之家  ›  专栏  ›  技术社区  ›  Sebastian Zolg

使用新的基于aad的访问控制通过web浏览器上的uri访问blob文件

  •  0
  • Sebastian Zolg  · 技术社区  · 6 年前

    宣布 Azure Storage support for Azure Active Directory based access control ,是否可以仅通过其uri在web浏览器上提供blob(特定文件)?

    我想简化的用例是让一些人可以访问blob上的文件,而无需向uri附加sas令牌。相反,当试图在他/她的web浏览器中打开普通uri时,启动典型的oauth流是非常明智的。

    在我的例子中,我们希望允许用户通过我们的支持bot(构建在Microsoft bot框架上)访问已上载到blob存储的文件。我们的支持系统中的链接应由支持代理在其选择的Web浏览器中访问。

    这个用例是否得到了这个公告的支持,或者这只对编码的oauth流有效,这意味着我们还需要实现一些代码?

    如果是,有没有一个很好的示例说明如何从azure函数应用程序启动oauth流并使用生成的令牌(通过azure存储rest端点)下载文件?

    0 回复  |  直到 6 年前
        1
  •  1
  •   Tony Ju    6 年前

    如果要对存储使用基于azure active directory的访问控制,则需要获取访问令牌。这些步骤供您参考。

    1. Register an application

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

    三。 Get the access token . enter image description here

    4、使用Access令牌,现在您可以调用存储REST API。 enter image description here

        2
  •  1
  •   Sebastian Zolg    6 年前

    同时 this answer 技术上是正确的,这不是对我最初问题的直接回答。

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

    在我的例子中,我们希望允许用户通过我们的支持bot(构建在Microsoft bot框架上)访问已上载到blob存储的文件。例如,将附件作为支持系统中的链接,供支持代理访问。

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

    随着azure storage对基于azure active directory的访问控制的支持的宣布,是否可以仅通过其uri在web浏览器上提供blob(特定文件)?

    ,这是不可能的。更具体地说,仅仅在浏览器中打开指向blob的直接uri并不会触发oauth流。相反,它总是会给你 ResourceNotFound 响应,除非您提供SAS查询令牌或将blob设置为public。从安全性的角度来看,这两种解决方案都是不好的(当涉及到普通用户时),而且显然是不好的用户体验。

    解决方案

    为了找到一种方法来准确地存档我想要的内容,我想出了一个azure函数的想法,通过传递 fileName 使用路由模板构造路径的uri。

    考虑到安全性和访问令牌的需要,您可以通过平台身份验证(也称为easyauth)来保护Function应用程序。

    然而 ,这还不够,而且配置解决方案的所有部分也不是一帆风顺的。这就是我分享的原因。

    tl;dr高级步骤:

    1. 创建新的功能应用程序(建议使用v2)
    2. 为启用功能应用程序 authentication (伊斯亚特)
    3. 为功能应用程序创建服务主体(也称为应用程序注册)(步骤2隐含)
    4. 添加其他允许的令牌访问群体 https://storage.microsoft.com 应用程序注册
    5. 编辑应用注册的清单以包含azure存储api权限(请参阅下面的特别说明)
    6. 修改azure资源管理器中的authsettings以包括 additionalLoginParams 对于令牌响应和resourceid
    7. 至少给 Storage Blob Data Reader 对访问文件的所有用户的blob权限
    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.Read ,这在大多数情况下是有帮助或需要的。

    在您上传了修改后的清单之后,您可以在 API权限 你的应用注册标签。

    由于easyauth正在使用aad的v1端点,因此您的应用程序需要通过传递 resource=https://storage.azure.com/ 当触发oauth流时。

    此外,azure存储需要认证头的承载模式,因此需要jwt令牌。要从端点获取jwt令牌,我们需要传递 response_type=code id_token 作为附加登录参数。

    两者只能通过 Azure Resource explorer 或者动力地狱。

    使用azure资源管理器,您必须一直导航到功能应用程序上的authsettings并设置 附加登录参数 因此。

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