繁体   English   中英

c# .NET 6.0 API 使用 Azure AD 令牌(从 Blazor 服务器端调用)返回 401 未授权

[英]c# .NET 6.0 API using Azure AD token (called from Blazor server side) returns 401 unautorized

我有一个 Blazor 服务器应用程序,它使用 Azure AD 登录。 这工作正常,但是当在我的 API controller 上设置 [Authorize] 时,controller 返回 401 未授权事件,尽管 Bearer 令牌已传递。

我可以看到令牌在 API 中传递,所以我有点卡住了。 配置问题或某些代码错误? 任何帮助,将不胜感激。

Blazor 项目的 Azure 配置:

Azure Blazor 设置(服务器端项目):

Azure 设置为 Blazor(服务器端项目)

Blazor 的授权设置: Blazor 的授权设置

为 Blazor 添加的秘密:为 Blazor 添加的秘密

Api Blazor 权限: Api Blazor 权限

API 项目的 Azure 配置:

Azure 设置为 API: Azure 设置为 API

API 的身份验证设置: API 的身份验证设置

API permissions for API (probably tooman, found a guide that suggested adding more): API permissions for API (probably tooman, found a guide that suggested adding more)

Exposed an API for API (type-o in admin.accsess): Exposed an API for API (type-o in admin.accsess)

blazor项目代码:

appsettings.json(一旦这个工作,新的秘密将被制作并移动到 keyvault):`

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "3c                           8c",
    "TenantId": "eb                            a0",
    "Scopes": "api://3c                        8c/admin.accsess" 
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

`

Startup.cs`

public void ConfigureServices(IServiceCollection services)
{
    services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
        .EnableTokenAcquisitionToCallDownstreamApi(new string[] { Configuration["AzureAd.Scopes"] })
        .AddInMemoryTokenCaches();
    
    services.AddHttpContextAccessor();

    services.AddControllersWithViews(options =>
    {
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    }).AddMicrosoftIdentityUI();

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

`

`

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }

`

测试 controller 以尝试调用 api(始终从 api 获得 401):`

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Diagnostics;
using Microsoft.Identity.Web;
using Newtonsoft.Json.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace S********.Controllers
{
    [Authorize]
    public class TenantController : Controller
    {
        private readonly IHttpClientFactory _clientFactory;
        private readonly ITokenAcquisition _tokenAcquisitionService;
        private HttpClient _client;

        public TenantController(IHttpClientFactory clientFactory, ITokenAcquisition tokenAcquisitionService)
        {
            _clientFactory = clientFactory;
            _tokenAcquisitionService = tokenAcquisitionService;
        }
        public IActionResult Index()
        {
            return View();
        }

        public async Task<IActionResult> GetStuff()
        {
            string[] scopes = { "api://3c*******************c8c/admin.accsess" };
            string accessToken = await _tokenAcquisitionService.GetAccessTokenForUserAsync(scopes);
            
            _client = _clientFactory.CreateClient();
            _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            var response = await _client.GetAsync("https://localhost:44394/api/SystemConfig/GetByName/System.Tenants");
            return Ok(response);
        }
    }
}

`

API项目代码:

应用设置.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "custodisas.onmicrosoft.com",
    "TenantId": "eb                      a0",
    "ClientId": "7d                       de",
    "ClientSecret": "G                     5",
    "CallbackPath": "/auth-response"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

启动.cs:

`

public void ConfigureServices(IServiceCollection services)
{
    var NlogSetup = new SetupLogger();
    NLogConfig = NlogSetup.SetUpLogger("C://Logtest//Skynet//SiloApi.txt").Result;
    LogManager.Configuration = NLogConfig;
    Logger = LogManager.GetLogger("SkynetAPI");
    Logger.Info("Logger up and running");

    services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme)
        .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));

    // Have tried both with and without scope as required claim
    var scopePolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        //.RequireClaim("scope", "api://3c7*****************c8c/admin.accsess")
        .Build();

    // Have tried both with and without adding scopepolicy as filter
    services.AddMvc(options =>
    {
        //options.Filters.Add(new AuthorizeFilter(scopePolicy));
        options.EnableEndpointRouting = false;
    });

    [...other services...]

    services.AddControllers();
    
    [...other services...]
}

`

`

   public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            app.UseMvc(routes =>
            {
                routes.MapRoute(name: "default", template: "api/{controller}/{action}/{id?}");
            });

        }

`

Controller 被调用(删除 [Authorize] 时返回正确的信息):`

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;
using Newtonsoft.Json;
using NLog;
using Orleans;
using *****.Grains.Interfaces;
using *****.Grains.Interfaces.Core.ApplicationScheduler;
using *****.Shared.Core;
using *****.Shared.Core.ApplicationScheduler;
using *****.Shared.Core.GrainStates;
using *****.Shared.Core.RunApplication;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Identity.Web;
using Microsoft.AspNetCore.Cors;
using System.IdentityModel.Tokens.Jwt;

namespace *****.API.Controllers
{

    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class SystemConfigController : MasterController
    {
        public SystemConfigController(Logger logger, IClusterClient clusterClient) : base(logger, clusterClient)
        {
        }

        [HttpGet]
        [Route("GetByName/{configGrainName}")]
        public async Task<ActionResult<string>> GetSystemConfigByName(string configGrainName)
        {
            // Tried adding this code to get the token, it is passed and I can see it when removing [Authorize]
            var re = Request;
            var headers = re.Headers;
            var tokenString = headers["Authorization"];
            Logger.Info("Token: \n\n" + tokenString + "\n\n");

            var tmpGrain = clusterClient.GetGrain<I*****>(0);
            return JsonConvert.SerializeObject(await tmpGrain.GetSystemGrainByName(configGrainName));
        }
    }
}

`

主控制器:`

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NLog;
using Orleans;

namespace *****.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class MasterController : ControllerBase
    {
        // TODO: Get from config.
        public Guid SystemID { get; set; } = Guid.Parse("a9****************71c");
        public Logger Logger;
        public IClusterClient clusterClient;

        public MasterController(Logger logger, IClusterClient client)
        {
            Logger = logger;
            clusterClient = client;
        }
    }
}

`

希望这是足够的信息。 我完全被卡住了,所以任何帮助将不胜感激。 谢谢:)

我试图在我的环境中重现。

给定 API 权限,向 API 申请并授予管理员同意您的申请。

在此处输入图像描述

  • 由于您在门户中给出了两个范围api://43xxxxxxx6b3edf/admin.accessapi://438xxxxx-a788-xxxx/user.access公开了一个 API 部分,因此它们都必须在代码中给出。

在此处输入图像描述

  • 我尝试使用 postman 仅提供一个 scope,但出现无效 scope 错误。这导致您的 api 访问中出现错误 401。

在此处输入图像描述

***对于 client_credentials scope 访问您的 api 必须是 api://<client_id>/.default

给上面的scope后我就可以成功拿到token了。***

在此处输入图像描述

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM