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

如何解决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。当我解决错误(或找到错误)时,我会更新此答案。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-