OBO 流程不适用于两个应用程序注册

问题描述 投票:0回答:1

我正在尝试配置 WebApp(MSAL + Razor Pages)应用程序以使用两个应用程序注册 - 一个用于前端,另一个用于后端。主要原因是,我计划将来迁移到 SPA,并希望过渡尽可能顺利(无需请求新的同意/新的应用程序注册)。我以与 SPA 应用程序类似的方式配置它们(两个注册,后端所需的所有 API 权限,在后端的“knownClientApplications”中配置的前端注册。系统具有前端注册,用于对范围 api://{backendClientId 的用户进行身份验证}/.默认范围。一切正常,同意已正确传播到后端注册。

我遇到 OBO 流程问题。我尝试将下游 API 配置为使用后端服务器的客户端 ID,但由于权限不足,对图形 API 的调用失败。以下是服务器端的配置:

            services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(o =>
                {
                    o.Instance = "https://login.microsoftonline.com/";
                    o.Domain = "{MyDomain}";
                    o.TenantId = "common";
                    o.ClientId = "{FrontendClientId}";
                    o.ClientSecret = "{FrontendClientSecret}";
                    o.CallbackPath = "/signin-oidc";
                    o.Scope.Add("api://{BackendClientId}/.default");
                })
                .EnableTokenAcquisitionToCallDownstreamApi(o =>
                {
                    o.Instance = "https://login.microsoftonline.com/";
                    o.TenantId = "common";
                    o.ClientId = "{BackendClientId}";
                    o.ClientSecret = "{BackendClientSecret}";
                })
                .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
                .AddInMemoryTokenCaches();

我做错了什么?我想要的设置是否可行?

非常感谢您的帮助。

azure authentication oauth-2.0 razor-pages azure-ad-msal
1个回答
0
投票

是的,这是可能的。而且,从安全角度来看是可取的。

在 OBO 流程中,您不应在前端的任何位置使用后端 ClientId 或 ClientSecret。您的后端应该获取前端身份的 OBO 令牌,以供后端 API 使用。

AddMicrosoftIdentityWebApp(o => ...)
设置凭证或令牌以向后端 API 进行身份验证。

EnableTokenAcquisitionToCallDownstreamApi
设置要传递到后端并由后端使用的 OBO 令牌。

因此,在这种情况下,后端将使用前端身份来调用 Graph API。因此,后端主体需要相关的“委托”图权限。前端将其凭证委托给后端。前端需要 Graph 的 app 权限,以便后端能够执行必要的操作。例如,如果前端需要读取用户名:

前端权限

:类型App,权限

User.ReadBasic.All

后端权限

:类型Delegated,权限

User.ReadBasic.All
您可以进一步完善安全性的一个示例是,所有登录到前端的用户都是 Entra 用户,并且您不使用前端令牌,而是使用登录用户的令牌。您使用 

acquireTokenSilent

执行此操作,然后您的前端不需要显式图形权限,除非它正在执行用户不允许的操作。但这是另一篇文章的事了。

您可以按如下方式更新代码:

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(o => { o.Instance = "https://login.microsoftonline.com/"; o.Domain = "{MyDomain}"; o.TenantId = "common"; o.ClientId = "{FrontendClientId}"; o.ClientSecret = "{FrontendClientSecret}"; o.CallbackPath = "/signin-oidc"; o.Scope.Add("api://{BackendClientId}/.default"); }) .EnableTokenAcquisitionToCallDownstreamApi(o => { o.Instance = "https://login.microsoftonline.com/"; o.TenantId = "common"; o.ClientId = "{FrontendClientId}"; o.ClientSecret = "{FrontendClientSecret}"; o.Scopes = new[] { "api://{BackendClientId}/.default" }; // Scopes needed to call Azure Function API }) .AddInMemoryTokenCaches(); .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))

以下是下游 API 调用的令牌获取示例:

private readonly ITokenAcquisition _tokenAcquisition; private readonly HttpClient _httpClient; public YourFrontendController(ITokenAcquisition tokenAcquisition, IHttpClientFactory httpClientFactory) { _tokenAcquisition = tokenAcquisition; _httpClient = httpClientFactory.CreateClient(); } public async Task<IActionResult> YourAction() { var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { "api://{BackendClientId}/.default" }); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = await _httpClient.GetAsync("https://{YourAzureFunction}.azurewebsites.net/api/{FunctionName}"); if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync(); return Ok(content); } return BadRequest(); }

以下是前端调用凭证验证、下游API获取OBO token的示例:

// Authenticate frontend services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"])) }; }); // Implement OBO flow private readonly ITokenAcquisition _tokenAcquisition; private readonly HttpClient _httpClient; public YourAzureFunction(ITokenAcquisition tokenAcquisition, IHttpClientFactory httpClientFactory) { _tokenAcquisition = tokenAcquisition; _httpClient = httpClientFactory.CreateClient(); } // Example using the me API using the delegated token in OBO flow [FunctionName("YourFunctionName")] public async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log) { string accessToken = await _tokenAcquisition.GetTokenOnBehalfOfUserAsync(req.Headers["Authorization"]); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = await _httpClient.GetAsync("https://graph.microsoft.com/v1.0/me"); if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync(); return new OkObjectResult(content); } return new BadRequestResult(); }

这个下游函数API有点仓促,但是你应该明白了。

© www.soinside.com 2019 - 2024. All rights reserved.