Azure SQL 通过 API 与 MS Identity Platform 以用户身份进行身份验证

问题描述

我有一个调用 ASP.NET Core 3.1 Web API 的 ASP.NET Core 3.1 Web 应用程序,它依次访问 Azure SQL 数据库。身份验证通过 MSAL(Microsoft 身份平台)提供 - 即使用相对较新的 Microsoft.Identity.WebMicrosoft.Identity.Web.UI 库。

目标是确保用户在他/她自己登录的上下文中通过 API 从 SQL 中提取数据,从而实现行级安全、访问审计和其他好处。

我已成功使登录过程适用于 Web 应用程序 - 并通过它获取有效的访问令牌以使用我在向 AD 注册后者时创建的范围访问 API。

当我从 Visual Studio 在本地运行 API 和应用程序时,一切都按预期工作 - 向应用程序提供了正确的访问令牌以访问 API,并提供 API 以访问 SQL - 在这两种情况下,在用户(即我的)身份。

但是,当我将 API 发布到 Azure 上的应用服务并从本地版本的 Web 应用或应用服务托管版本访问它时,API 用于访问 SQL 的访问令牌包含API 的应用程序身份(系统分配的托管身份),而不是用户的身份。虽然我可以作为应用程序访问SQL,但这不是我们需要的。

Web 应用程序使用 GetAccessTokenForUserAsyncITokenAcquisition 方法获取其访问令牌 - 将我为 API 定义的单一范围作为参数。

API 像这样获取它的令牌(以访问 SQL):

var token = await new AzureServiceTokenProvider().GetAccessTokenAsync("https://database.windows.net",_tenantId)

...其中 _tenantId 是订阅的租户 ID。

我已向 API 的 AD 注册添加了 SQL Azure 数据库“user_impersonation”API 权限 - 但这并没有帮助。顺便说一句,出于某种原因,Azure 将此权限的全名为 https://sql.azuresynapse.usgovcloudapi.net/user_impersonation - 这有点令人担忧,因为这只是一个位于英国的常规 Azure 帐户。

我发现了一些与此类似的帖子,但主要是针对旧版本的解决方案集。我希望避免必须编写自己的代码来发布令牌请求 - 这应该由 MSAL 库处理。

我应该在登录后以某种方式分别从 Web 应用程序请求 SQL 访问范围,还是 API 应该做一些不同的事情来获取标识用户的 SQL 访问令牌?为什么在本地运行时能完美运行?

这似乎应该是一个非常常见的用例(最常见的?)但几乎没有记录 - 我发现的大多数文档仅涉及正在使用的应用程序标识,或者没有告诉您要做什么这个特殊的技术栈。

解决方法

最后——成功!最后,这是文档的关键部分:Microsoft identity platform and OAuth 2.0 On-Behalf-Of flow - 关键点是:

  1. 该应用仅需要令牌才能访问 API。
  2. 然后,API 代表通过第一个令牌标识的用户请求令牌以访问 SQL。

关键在于 - 由于 API 无法触发第二步的同意窗口 - 我必须使用 Azure 门户中的企业应用程序选项卡来预授予 SQL 权限。

所以好消息是它确实有效:也许对某些人来说很明显,但 IMO 花了太长时间才找到答案。我会在适当的时候写一篇关于如何做到这一点的更全面的解释,因为这不仅仅是我在努力解决这个问题。

坏消息是 - 在我的调查过程中 - 我发现 Azure B2C(这是我接下来需要添加的内容)不支持这种“代表”流程 - click here for details .这是一个巨大的耻辱,因为我认为这是它最明显的用例!哦,好吧,回到绘图板。

,

我目前正在使用 Net5.0 Web 应用解决类似问题。它似乎在本地工作的原因是您使用可以访问 Azure SQL 的用户登录到 Visual Studio,而这些是您在 Db 中获得的权限。 IDE 使用这些凭据代替托管服务标识,后者会在您将应用上传到 Azure 时使用。

如您所见,在应用注册中,您需要为 Azure SQL 数据库 user_impersonation 的应用授予权限。

在您的代码中,您需要从 https://database.windows.net//.default 请求令牌(注意 // 因为 v1 端点需要它)。通过引用 /.default,您是在请求您在应用注册门户中为该应用选择的所有权限。

https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#the-default-scope

在 Startup.cs 中,您需要使用所需范围的 EnableTokenAcquisitionToCallDownstreamApi。

        services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
            .EnableTokenAcquisitionToCallDownstreamApi(new[]
               {"https://database.windows.net//.default"})
            // Adds the User and App InMemory Token Cache 
            .AddInMemoryTokenCaches();

        services.AddAuthorization(options =>
        {
            // By default,all incoming requests will be authorized according to the 
            // default policy
            options.FallbackPolicy = options.DefaultPolicy;
        });

        services.AddDbContext<MyDatabaseContext>(options =>
               options.UseSqlServer(
               Configuration.GetConnectionString("MyAzureConnection")));

        // The database interface
        services.AddScoped<ITodos,TodoData>();

        services.AddRazorPages()
            .AddRazorRuntimeCompilation()
            .AddMvcOptions(o => 
            {
                var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
                o.Filters.Add(new AuthorizeFilter(policy));
            })
            .AddMicrosoftIdentityUI();

您还需要使用 [AuthorizeForScopes(Scopes = new string[]{"https://database.windows.net//.default"}] 装饰您的控制器并包含该控制器所需的范围。对于 Razor,它位于页面模型的顶部,并且需要引用“使用 Microsoft.Identity.Web;”

namespace ToDoApp.Pages.Todos
{
    [AuthorizeForScopes(ScopeKeySection = "AzureSQL:BaseUrl")]
    public class CreateModel : PageModel

我将 appsettings.json 中的一个部分用于范围并使用 ScopeKeySection 检索它:

  "AzureSQL": {
    "BaseUrl": "https://database.windows.net//.default","Scopes": "user_impersonation"
  }

这向您展示了将其包含在 MVC、Razor 和 Blazor 的何处:

https://github.com/AzureAD/microsoft-identity-web/wiki/Managing-incremental-consent-and-conditional-access#in-mvc-controllers

最后,您的 DbContext 需要一个令牌,您可以将其从客户端应用程序传递给它(也许......)。

这就是我目前的做法

    public class MyDatabaseContext : DbContext
    {
        private readonly ITokenAcquisition _tokenAcquisition;

        public MyDatabaseContext (ITokenAcquisition tokenAcquisition,DbContextOptions<MyDatabaseContext> options)
            : base(options)
        {

            _tokenAcquisition = tokenAcquisition;
            string[] scopes = new[]{"https://database.windows.net//.default"};

            var result = _tokenAcquisition.GetAuthenticationResultForUserAsync(scopes)
                            .GetAwaiter()
                            .GetResult();
            token = result.AccessToken;

            var connection = (SqlConnection)Database.GetDbConnection();
            connection.AccessToken = result.token;
        }

这是一个有缺陷的解决方案。如果我重新启动应用程序并尝试再次访问它,我会收到错误 Microsoft.Identity.Web.MicrosoftIdentityWebChallengeUserException: IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user

好像跟TokenCache有关。如果我退出并再次登录或清除浏览器缓存,错误将得到解决。我有一个解决方法可以在应用程序失败时登录,但由于我使用的是应用程序的凭据,因此存在不足。

但是,它以用户而不是具有用户权限的应用程序成功连接到 Azure SQL Db。当我解决错误(或找到错误)时,我会更新此答案。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...