Каскадная политика авторизации в Blazor не работает

Я работаю над новым проектом, в котором будут содержаться некоторые подробные правила того, что пользователь может и не может получить / увидеть, с Identity Server 4.

Я пытаюсь использовать AuthorizeView с политиками, чтобы скрыть параметры в моей навигации, но представления каскадные, что означает, что у меня есть что-то вроде этого:

<MatNavMenu>
<MatNavItem Href="/home" Title="Home"><MatIcon Icon="@MatIconNames.Home"></MatIcon>&nbsp; Home</MatNavItem>
<MatNavItem Href="/claims" Title="Claims"><MatIcon Icon="@MatIconNames.Vpn_key"></MatIcon>&nbsp; Claims</MatNavItem>
<AuthorizeView Policy="@PolicyNames.IdentitySystemAccess">
    <Authorized>
        <AuthorizeView Policy="@PolicyNames.AccessManagement">
            <Authorized>
                <MatNavSubMenu @bind-Expanded="@_accessSubMenuState">
                    <MatNavSubMenuHeader>
                        <MatNavItem AllowSelection="false">&nbsp; Access Management</MatNavItem>
                    </MatNavSubMenuHeader>
                    <MatNavSubMenuList>
                        <AuthorizeView Policy="@PolicyNames.User">
                            <Authorized>
                                <MatNavItem Href="users" Title="users"><MatIcon Icon="@MatIconNames.People"></MatIcon>&nbsp; Users</MatNavItem>
                            </Authorized>                               
                        </AuthorizeView>
                        <AuthorizeView Policy="@PolicyNames.Role">
                            <Authorized>
                                <MatNavItem Href="roles" Title="roles"><MatIcon Icon="@MatIconNames.Group"></MatIcon>&nbsp; Roles</MatNavItem>
                            </Authorized>
                        </AuthorizeView>
                    </MatNavSubMenuList>
                </MatNavSubMenu>
            </Authorized>
        </AuthorizeView>
    </Authorized>
</AuthorizeView>

Я проверил, что утверждения, необходимые для выполнения определенных политик, присутствуют после того, как пользователь вошел в систему, но по какой-то причине AuthorizeView не работает.

Я обновил свой App.Razor, чтобы использовать AuthorizeRouteView. Есть идеи относительно того, почему это происходит?

Примечание. Я использую утверждения, назначенные роли, но они являются динамическими, и я не могу использовать policy.RequireRole (my-role) в своих политиках, поэтому я использую:

options.AddPolicy(PolicyNames.User, b =>
                {
                    b.RequireAuthenticatedUser();
                    b.RequireClaim(CustomClaimTypes.User, "c", "r", "u", "d");
                });

Когда мое приложение запускается, ни один из элементов меню не отображается, за исключением элементов дома и утверждений, которые не защищены AuthorizeView.


person Steve    schedule 23.11.2020    source источник
comment
У меня такая же проблема .. Решили?   -  person Mårshåll    schedule 22.12.2020
comment
Привет, Маршалл, ваши утверждения также похожи на мои в примере, где одно утверждение имеет несколько значений?   -  person Steve    schedule 24.12.2020
comment
Да! Я пробовал разными способами! ClaimType с несколькими значениями, расширение, проверяющее несколько утверждений. Я работаю с несколькими макетами, не знаю, будет ли это проблемой ... На домашней странице я смог проверить политику на основе ролей. Я просто не могу этого сделать с претензиями.   -  person Mårshåll    schedule 24.12.2020
comment
Вы пробовали только что предоставленное мной решение? И также я предполагаю, что вы используете политики.   -  person Steve    schedule 24.12.2020
comment
Привет, Стив, теперь я увидел твое решение. Если я правильно понимаю, проблема в том, что клиент Blazor не может расшифровать утверждения в массиве, и мне придется их разделять? Я не использую IS4, я использую Microsoft Identity со стандартным jwt. Постараюсь внести это улучшение в сборку претензий к клиенту.   -  person Mårshåll    schedule 24.12.2020
comment
Да, это правильно, я думаю, что это решение работает независимо от того, используете ли вы IS4 или нет, ссылки, необходимые для ClaimsPrincipalFactory, принадлежат Microsoft, а не уникальны для IS4. Я добавил пространства имен в свой пример   -  person Steve    schedule 24.12.2020
comment
Чувак, ты УДИВИТЕЛЬНЫЙ! Большое вам спасибо !!! Отлично работает ... Я делаю что-то попроще ... Я тоже опубликую свое решение, чтобы помочь другим ... С праздником!   -  person Mårshåll    schedule 24.12.2020


Ответы (2)


Проблема возникла из-за того, что в настоящее время Blazor не поддерживает чтение заявлений, которые отправляются в виде массивов.

например пользователь: [c, r, u, d]

Не читается.

Чтобы исправить это, вам необходимо добавить ClaimsPrincipalFactory.

e.g.

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

namespace YourNameSpace
{
    public class ArrayClaimsPrincipalFactory<TAccount> : AccountClaimsPrincipalFactory<TAccount> where TAccount : RemoteUserAccount
    {
        public ArrayClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
        { }


        // when a user belongs to multiple roles, IS4 returns a single claim with a serialised array of values
        // this class improves the original factory by deserializing the claims in the correct way
        public async override ValueTask<ClaimsPrincipal> CreateUserAsync(TAccount account, RemoteAuthenticationUserOptions options)
        {
            var user = await base.CreateUserAsync(account, options);

            var claimsIdentity = (ClaimsIdentity)user.Identity;

            if (account != null)
            {
                foreach (var kvp in account.AdditionalProperties)
                {
                    var name = kvp.Key;
                    var value = kvp.Value;
                    if (value != null &&
                        (value is JsonElement element && element.ValueKind == JsonValueKind.Array))
                    {
                        claimsIdentity.RemoveClaim(claimsIdentity.FindFirst(kvp.Key));

                        var claims = element.EnumerateArray()
                            .Select(x => new Claim(kvp.Key, x.ToString()));

                        claimsIdentity.AddClaims(claims);
                    }
                }
            }

            return user;
        }
    }
}

Затем зарегистрируйте это в своей программе / автозагрузке (в зависимости от того, используете ли вы .core или нет) следующим образом:

builder.Services.AddOidcAuthentication(options =>
        {
            builder.Configuration.Bind("oidc", options.ProviderOptions);
        })
        .AddAccountClaimsPrincipalFactory<ArrayClaimsPrincipalFactory<RemoteUserAccount>>();
person Steve    schedule 24.12.2020

Поняв проблему со Стивом, я принял следующее решение. Полезно для тех, кто следит за документация

Я обновляю свой метод для анализа заявок из jwt, чтобы отделить весь массив заявок!

private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
    var claims = new List<Claim>();
    var payload = jwt.Split('.')[1];
    var jsonBytes = ParseBase64WithoutPadding(payload);
    var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);

    keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);

    if (roles != null)
    {
        if (roles.ToString().Trim().StartsWith("["))
        {
            var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());
            foreach (var parsedRole in parsedRoles)
            {
               claims.Add(new Claim(ClaimTypes.Role, parsedRole));
            }
        }
        else
        {
            claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
        }
        keyValuePairs.Remove(ClaimTypes.Role);
    }
    claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));
    for (int i = 0; i < claims.Count; i++)
    {
        var name = claims[i].Type;
        var value = claims[i].Value;
        if (value != null && value.StartsWith("["))
        {
            var array = JsonSerializer.Deserialize<List<string>>(value);
            claims.Remove(claims[i]);
            foreach (var item in array)
            {
                claims.Add(new Claim(name, item));
            }
        }
    }
    return claims;
}
person Mårshåll    schedule 24.12.2020