ASP.NET Core MVC,Azure基于角色的身份验证 - Authorize属性失败 - 没有返回角色声明?

3

我需要构建一个使用.NET6的ASP.NET Core Web应用程序。我已经在GitHub上遵循了Azure-Sample存储库中的说明:

我已经调整了appsettings.json文件中的AzureAd部分:

 "AzureAd": {
        "ClientId": "my-client-guid",
        "TenantId": "my-tenant-guid",
        "Domain": "my-tenant-name",
        "ClientSecret": "my-client-secret-guid",
        "Instance": "https://login.microsoftonline.com/",
        "Scopes": "user.read,profile,openid",
        "ClientCapabilities": [ "cp1" ],
        "CallbackPath": "/signin-oidc"
    }
  • ClientIdTenantId字段从Azure门户AD应用程序面板中获取,我在该面板上创建了应用程序注册。在此应用程序的详细信息页面上是概览面板 - 提供ClientId、TenantId的参考。
  • ClientSecret也是在同一页面上创建的,在证书和密码面板上。
  • 域名Azure域名列表面板提供。

用户角色(读取器/管理员)是在上述(Azure AD应用程序)页面上创建的。

仍然在同一页上:

  • 身份验证选项卡中添加了一个新的Web平台,重定向URI为https://localhost:7293/signin-oidc。对于前端通道注销,提供了https://localhost:44321/signout-oidc
  • ID Tokens隐式授权和混合流程部分下被选中。
  • 单租户选项(仅允许组织帐户)。
  • 公共流程被禁用。

企业应用程序中,我的应用程序已列出。在应用程序的详细信息视图中:

  • 属性选项卡:分配要求设置为false。我不想将用户预分配给该应用程序。
  • 对用户可见:设置为false。
  • 用户和组选项卡:安全组已创建,角色(读取器/管理员)已分配给安全组。成员已分配到安全组。为了测试目的,我还添加了我的Azure AD用户,并赋予了读取器角色。
  • 单点登录:这里我看到以下消息:在企业应用程序体验中,此应用程序的单点登录配置不可用。{{my-application-name}}是使用应用程序注册体验创建的
  • 登录日志:奇怪的是,我在这里看到我的登录记录,因此我认为角色声明未转移到我的Web应用程序,因为在打开控制器视图时我收到未经授权的错误。

Web应用程序引用

我已将配置部分映射到一个模型中,以便在需要时注入为IOptionsMicrosoftGraphConfiguration,以映射GraphApiUrl:

public class MicrosoftGraphConfiguration
{
    public const string ConfigurationName = "MicrosoftGraph";
    public string? GraphApiUrl { get; set; }
}

AzureAdConfiguration 用于映射 AzureAd 配置。

public class AzureAdConfiguration
{
    public const string ConfigurationName = "AzureAd";
    public string? ClientId { get; set; }
    public string? TenantId { get; set; }
    public string? ClientSecret { get; set; }
    public string? Domain { get; set; }
    public string? Instance { get; set; }
    public string? BaseUrl { get; set; }
    public string[]? ClientCapabilities { get; set; }
    public string[]? Scopes { get; set; }
    public string? CallbackPath { get; set; }
}

Program.cs 用于配置和添加服务到 DI 容器中等。

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<MicrosoftGraphConfiguration>(builder.Configuration.GetSection(MicrosoftGraphConfiguration.ConfigurationName));
builder.Services.Configure<AzureAdConfiguration>(builder.Configuration.GetSection(AzureAdConfiguration.ConfigurationName));

JwtSecurityTokenHandler.DefaultMapInboundClaims = false;

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, subscribeToOpenIdConnectMiddlewareDiagnosticsEvents: true)
    .EnableTokenAcquisitionToCallDownstreamApi(new string[] { "User.Read" })
    .AddInMemoryTokenCaches();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AssignmentToReaderRoleRequired", policy => policy.RequireRole("Reader"));
    options.AddPolicy("AssignmentToAdminRoleRequired", policy => policy.RequireRole("Admin"));
});

builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.TokenValidationParameters.RoleClaimType = "roles";
});

// Add services to the container.
builder.Services.AddControllersWithViews(options =>
{
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();

    options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

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

app.UseRouting();

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

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

用于测试目的的控制器操作:

[HttpGet]
public async Task<IActionResult> IndexAsync()
{
    GetTenantDetailsResult tenantDetails = new();
    
    if (this.tenantService != null)
    {
        tenantDetails = await this.tenantService.GetTenantDetailsAsync();
    }

    return View(new TenantViewModel(tenantDetails));
}

[HttpGet]
[Route("/Reader")]
[Authorize(Policy = AuthorizationPolicies.AssignmentToReaderRoleRequired)]
public IActionResult Reader()
{
    var asd = User;
    return Ok("He is a reader");
}

[HttpGet]
[Route("/Admin")]
[Authorize(Policy = AuthorizationPolicies.AssignmentToAdminRoleRequired)]
public IActionResult Admin()
{
    return Ok("He is an admin");
}

打开应用程序(无cookie - 隐身)会将我重定向到https://login.microsoft.com/{my-tenantId}/oauth2/authorize?...。一旦登录,没有[Authorize(...)]属性的端点可以正常工作。
打开任何具有[Authorize]属性的上述端点都会给我HTTP 404并重定向到: https://localhost:7293/MicrosoftIdentity/Account/AccessDenied?ReturnUrl=%2Freader 根据文档,角色声明应与任何ID令牌一起发送。 “这些分配的应用程序角色包含在为应用程序发行的任何令牌中,无论是...还是在您的应用程序登录用户时的ID令牌。”
我不确定,因此我的问题是:
1. 角色声明未发送? 2. 发送了角色声明但无法解析 3. 如何进行调试以查找问题
作为奇怪的行为...如果我在隐身/InPrivate浏览器窗口中打开应用程序/Reader端点,则在以下错误详细信息之后,我会收到HTTP 500响应代码: MsalServiceException:配置问题正在阻止身份验证-检查服务器的错误消息以获取详细信息。您可以在应用程序注册门户中修改配置。请参见https://aka.ms/msal-net-invalid-client了解详细信息。原始异常:AADSTS7000215:提供的客户端密码无效。确保在请求中发送的密钥是客户端密码值,而不是添加到应用程序的客户端密码ID的密钥。
即使ClientSecret如上所述,我认为它是正确的。 Http 500的原因可能是当前我没有维护https://localhost:7293/signin-oidc的端点。
感谢帮助,因为我已经迷失了3天。

我已经查看了https://stackoverflow.com/questions/34921391/asp-net-mvc-c-sharp-authorizeroles-not-working,但我不需要自定义角色提供程序。我在Azure租户中维护角色。 - Neophyte
我还检查了生成的客户端证书的过期日期。它有效期至2024年底。 - Neophyte
1个回答

1
我在我的环境中尝试复制相同的操作,并获得了以下结果: 我注册了一个名为“WebApp-RolesClaims”的Azure AD应用程序,按照GitHub上提到的相同步骤进行了操作。 我已经根据您的喜好将appsettings.json文件的进行了相应调整。
 "AzureAd": {
        "Instance": "https://login.microsoftonline.com/",
        "Domain": "my-tenant-name",
        "TenantId": "my-tenant-guid",
        "ClientId": "my-client-guid",
        "CallbackPath": "/signin-oidc"
        "SignedOutCallbackPath ": "/signout-callback-oidc",
        "Scopes": "user.read,profile,openid",
         // To call an API
        "ClientSecret": "my-client-secret-guid"        
    }

enter image description here

现在我运行了示例,它带我进入了Microsoft登录页面。

当我使用Azure AD用户凭据登录时,我得到了如下的同意屏幕

enter image description here

接受上述同意后,它给了我与你相同的异常,如下所示:

enter image description here

正如错误消息所说,如果你包含Client secret ID而不是Client secret value,通常会出现这种情况。

解决此错误,你需要将"ClientSecret"替换为以下内容:

enter image description here

注意,密钥值仅在创建密钥的几秒钟内可见,以后无法检索。

如果是这种情况,请删除现有的密钥并创建一个新的密钥,以复制其value,如下所示:

enter image description here

我已经通过以下方式替换了appsettings.json文件中的Client Secret value

enter image description here

现在我运行示例,我得到了已登录用户的声明,包括roles,如下所示:

enter image description here

因此,请尝试在你的appsettings.json文件中使用密钥值更改Client Secret


1
谢谢!确实这是主要问题,而且还发现域名不正确(我们有多个注册的替身)。我不得不从mydomain.com更改为mydomain.onmicrosoft.com才能使其正常工作。 - Neophyte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接