2

我有一个调用 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,并为访问 SQL 提供了 API - 在这两种情况下,都是在用户(即我的)身份下.

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

GetAccessTokenForUserAsyncWeb 应用程序使用以下方法获取其访问令牌ITokenAcquisition- 将我为 API 定义的单个范围作为参数。

API 获取其令牌(以访问 SQL),如下所示:

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

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

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

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

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

看起来这应该是一个非常常见的用例(最常见的?),但它几乎没有记录 - 我发现的大多数文档仅指正在使用的应用程序身份,或者没有告诉您该特定技术该怎么做堆。

4

2 回答 2

1

最后——成功!最后,这是文档的关键部分:Microsoft 身份平台和 OAuth 2.0 On-Behalf-Of 流程- 关键点是:

  1. 该应用程序只要求一个令牌来访问 API。
  2. 然后,API 代表通过第一个令牌识别的用户请求一个令牌来访问 SQL。

关键是——因为 API 无法触发第二步的同意窗口——我不得不使用 Azure 门户中的“企业应用程序”选项卡来预先授予 SQL 权限。

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

坏消息是 - 在我的调查过程中 - 我发现 Azure B2C(这是我需要添加的下一个内容)不支持此“代表”流程 -单击此处了解详细信息。这是一个很大的耻辱,因为我认为这是它最明显的用例!哦,好吧,回到绘图板。

于 2021-02-15T10:00:49.320 回答
0

我目前正在使用 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。当我解决错误(或找到错误)时,我将更新此答案。

于 2021-02-11T22:00:02.953 回答