简体   繁体   English

Blazor WASM 应用程序未正确解释角色

[英]Roles not being interpreted correctly by Blazor WASM app

I'm building an app using Blazor WASM (which I'm new to.) I am using Auth0 for security.我正在使用 Blazor WASM(我是新手)构建一个应用程序。我使用 Auth0 来保证安全。 When I get the roles claim down to the front end Razor page, it does not interpret the roles correctly, and I'm about at my wits' end.当我将角色声明放到前端 Razor 页面时,它没有正确解释角色,我几乎束手无策。

So, Starting from the server.所以,从服务器开始。 I use JwtBearer so we can authenticate a couple of APIs using roles.我使用 JwtBearer,因此我们可以使用角色对几个 API 进行身份验证。 That part works.那部分有效。

In the server's Program.cs:在服务器的 Program.cs 中:

...
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, c =>
    {
        c.Authority = builder.Configuration["Auth0:Domain"];
        c.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
        {
            ValidAudience = builder.Configuration["Auth0:Audience"],
            ValidIssuer = builder.Configuration["Auth0:Domain"]
        };
    });

var app = builder.Build();
...

Next, the client apparently wants to use OIDC?接下来,客户显然想使用 OIDC? Okay, weird, but fine...好吧,很奇怪,但是很好……

Client Program.cs客户端程序.cs

builder.Services.AddOidcAuthentication(options =>
{
    builder.Configuration.Bind("Auth0", options.ProviderOptions);
    options.ProviderOptions.ResponseType = "code";
    options.ProviderOptions.AdditionalProviderParameters.Add("audience", builder.Configuration["Auth0:Audience"]);
    // Without this setting, Blazor wants the role claim to be http://schemas.microsoft.com/ws/2008/06/identity/claims/role.
    options.UserOptions.RoleClaim = "https://thiscompany.com/roles";
});

await builder.Build().RunAsync();

Finally, the Razor page.最后是 Razor 页面。 In this example, I wanted to have it show a login prompt if the user is not logged in (login being detected as Authorization with no roles), while only Admins can see the "hello world" message;在这个例子中,我想让它在用户未登录时显示登录提示(登录被检测为没有角色的授权),而只有管理员可以看到“hello world”消息; it displays an unauthorized message to logged in users with any other roles.它向具有任何其他角色的登录用户显示未经授权的消息。 I've added some instrumentation to that message so I can see exactly what it's doing.我在该消息中添加了一些工具,因此我可以确切地看到它在做什么。

<AuthorizeView>
    <Authorized>
        <AuthorizeView Roles="Admin">
            <Authorized Context="loggedInUser">
                <PageTitle>Administration Center</PageTitle>
                <h1>Hello, world!</h1>
            </Authorized>
            <NotAuthorized Context="loggedInUser">
                <p>YOU ARE NOT WELCOME HERE</p>
                <p>Claims: <ul>
                    @for(int i = 0; i < Claims(context).Count; i++)
                    {
                        <li>@Claims(context)[i]</li>
                    }
                    </ul>
                </p>
                <p>Role claim: @RoleClaim(context)</p>
                <p>Is Admin in @GetRoleClaimType(context)? @context.User.IsInRole("Admin")</p>
            </NotAuthorized>
        </AuthorizeView>
    </Authorized>
    <NotAuthorized>
        <PageTitle>The Admin-Only App</PageTitle>
        <h1>The Admin-Only App</h1>
        Please log in to access this application.
    </NotAuthorized>
</AuthorizeView>

If I log in and I have the Admin role, I should see "Hello world".如果我登录并拥有管理员角色,我应该会看到“Hello world”。 Instead, here's what happens:相反,会发生以下情况:

YOU ARE NOT WELCOME HERE你在这里不受欢迎

Claims:索赔:

https://thiscompany.com/roles : ["Admin","SomeOtherRole"] https://thiscompany.com/roles :["Admin","SomeOtherRole"]

<...other irrelevant claims...> <...其他不相关的声明...>

Role claim: https://thiscompany.com/roles:["Admin","SomeOtherRole"]角色声明: https://thiscompany.com/roles:["Admin","SomeOtherRole"]

Is Admin in https://thiscompany.com/roles ?管理员在https://thiscompany.com/roles吗? False错误的

My understanding was that it was supposed to decompose the roles array and grant me access, but it doesn't.我的理解是它应该分解角色数组并授予我访问权限,但事实并非如此。 If I tinker with Auth0 so that I get the claim:如果我修改 Auth0 以便获得索赔:

https://thiscompany.com/roles: Admin

Then it works correctly, so I have a sneaking suspicion that the JSON array is getting encoded so that it decodes as a text string.然后它工作正常,所以我有一个偷偷摸摸的怀疑 JSON 数组正在被编码,以便它解码为文本字符串。 However, using the array of roles as provided is a requirement.但是,使用提供的角色数组是一项要求。 I'm really lost in the weeds here and not sure where or how to adjust this so that Blazor can understand the roles claim.我真的迷失了这里的杂草,不知道在哪里或如何调整它,以便 Blazor 可以理解角色声明。

I found the solution.我找到了解决方案。 The problem is that Blazor doesn't decompose the roles array, it just takes the raw text and interprets that as the name of the role rather than a JSON object it needs to handle.问题是 Blazor 不分解角色数组,它只是获取原始文本并将其解释为角色的名称,而不是它需要处理的 JSON object。 Blazor expects multiple role claims with the same type, one role per claim. Blazor 期望具有相同类型的多个角色声明,每个声明一个角色。

To decompose the role claim for it, you have to create a custom factory.要为其分解角色声明,您必须创建一个自定义工厂。

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using System.Security.Claims;
using System.Text.Json;

public class CustomUserFactory : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
    {
    }

    public async override ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var user = await base.CreateUserAsync(account, options);
        var claimsIdentity = (ClaimsIdentity?)user.Identity;

        if (account != null && claimsIdentity != null)
        {
            MapArrayClaimsToMultipleSeparateClaims(account, claimsIdentity);
        }

        return user;
    }

    private void MapArrayClaimsToMultipleSeparateClaims(RemoteUserAccount account, ClaimsIdentity claimsIdentity)
    {
        foreach (var prop in account.AdditionalProperties)
        {
            var key = prop.Key;
            var value = prop.Value;
            if (value != null && (value is JsonElement element && element.ValueKind == JsonValueKind.Array))
            {
                // Remove the Roles claim with an array value and create a separate one for each role.
                claimsIdentity.RemoveClaim(claimsIdentity.FindFirst(prop.Key));
                var claims = element.EnumerateArray().Select(x => new Claim(prop.Key, x.ToString()));
                claimsIdentity.AddClaims(claims);
            }
        }
    }
}

Then you add it to the services immediately after the AddOidcAuthentication call.然后在调用 AddOidcAuthentication 之后立即将其添加到服务中。

builder.Services.AddOidcAuthentication(options =>
{
    builder.Configuration.Bind("Auth0", options.ProviderOptions);
    options.ProviderOptions.ResponseType = "code";
    options.ProviderOptions.AdditionalProviderParameters.Add("audience", builder.Configuration["Auth0:Audience"]);
    // Without this setting, Blazor wants the role claim to be http://schemas.microsoft.com/ws/2008/06/identity/claims/role.
    options.UserOptions.RoleClaim = "https://thiscompany.com/roles";
});
builder.Services.AddApiAuthorization().AddAccountClaimsPrincipalFactory<CustomUserFactory>();

await builder.Build().RunAsync();

Thanks to Marinko Spasojevic for laying out the problem and solution in his article on Code Maze (in the "Supporting Multiple Roles" section).感谢 Marinko Spasojevic 在他关于 Code Maze 的文章(在“支持多个角色”部分)中提出问题和解决方案。 https://code-maze.com/using-roles-in-blazor-webassembly-hosted-applications/ https://code-maze.com/using-roles-in-blazor-webassembly-hosted-applications/

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

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