Лично я думаю, что вам нужно полностью отказаться от использования атрибута [Authorize]
.Понятно, что ваши требования к авторизации более сложны, чем тот метод, для которого он был готов.
Также в вопросе о том, как мне кажется, аутентификация и авторизация используются взаимозаменяемо.Здесь мы имеем дело с Авторизацией.
Поскольку вы используете авторизацию на основе идентификационных данных и утверждений.Я бы посмотрел на это "на лету", так сказать.Наряду с утверждениями вы могли бы использовать динамическую генерацию политики, а также авторизацию на основе сервисов, используя IAuthorizationRequirement
экземпляры для создания сложных правил и требований.
Углубление в реализации этого является большой темой, но естьнекоторые очень хорошие ресурсы доступны.Первоначальный подход (который я использовал сам) был первоначально детально описан Домом и Броком из IdentityServer слава.
Они сделали всестороннюю видео-презентацию по этому вопросу в NDC в прошлом году, которую вы можете посмотреть здесь .
Основываясь на концепциях, обсуждаемых в этом видео, Джерри Пелсер писал о близкой реализации, которую вы можете прочитать здесь .
Общие компонентыявляются:
Атрибуты [Authorize] будут заменены генератором политик, например:
public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
private readonly IConfiguration _configuration;
public AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options, IConfiguration configuration) : base(options)
{
_configuration = configuration;
}
public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
// Check static policies first
var policy = await base.GetPolicyAsync(policyName);
if (policy == null)
{
policy = new AuthorizationPolicyBuilder()
.AddRequirements(new HasScopeRequirement(policyName, $"https://{_configuration["Auth0:Domain"]}/"))
.Build();
}
return policy;
}
}
А затем вы создадите все экземпляры IAuthorizationRequirement
, необходимые для обеспечения правильной авторизации пользователей,Примером этого может быть что-то вроде:
public class HasScopeRequirement : IAuthorizationRequirement
{
public string Issuer { get; }
public string Scope { get; }
public HasScopeRequirement(string scope, string issuer)
{
Scope = scope ?? throw new ArgumentNullException(nameof(scope));
Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
}
}
Дом и Брок затем также детализируют клиентскую реализацию, которая связывает все это вместе, что может выглядеть примерно так:
public class AuthorisationProviderClient : IAuthorisationProviderClient
{
private readonly UserManager<ApplicationUser> userManager;
private readonly RoleManager<IdentityRole> roleManager;
public AuthorisationProviderClient(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager)
{
this.userManager = userManager;
this.roleManager = roleManager;
}
public async Task<bool> IsInRole(ClaimsPrincipal user, string role)
{
var appUser = await GetApplicationUser(user);
return await userManager.IsInRoleAsync(appUser, role);
}
public async Task<List<Claim>> GetAuthorisationsForUser(ClaimsPrincipal user)
{
List<Claim> claims = new List<Claim>();
var appUser = await GetApplicationUser(user);
var roles = await userManager.GetRolesAsync(appUser);
foreach (var role in roles)
{
var idrole = await roleManager.FindByNameAsync(role);
var roleClaims = await roleManager.GetClaimsAsync(idrole);
claims.AddRange(roleClaims);
}
return claims;
}
public async Task<bool> HasClaim(ClaimsPrincipal user, string claimValue)
{
Claim required = null;
var appUser = await GetApplicationUser(user);
var userRoles = await userManager.GetRolesAsync(appUser);
foreach (var userRole in userRoles)
{
var identityRole = await roleManager.FindByNameAsync(userRole);
// this only checks the AspNetRoleClaims table
var roleClaims = await roleManager.GetClaimsAsync(identityRole);
required = roleClaims.FirstOrDefault(x => x.Value == claimValue);
if (required != null)
{
break;
}
}
if (required == null)
{
// this only checks the AspNetUserClaims table
var userClaims = await userManager.GetClaimsAsync(appUser);
required = userClaims.FirstOrDefault(x => x.Value == claimValue);
}
return required != null;
}
private async Task<ApplicationUser> GetApplicationUser(ClaimsPrincipal user)
{
return await userManager.GetUserAsync(user);
}
}
Хотяэта реализация не отвечает вашим точным требованиям (что было бы трудно сделать в любом случае), это почти наверняка подход, который я выберуПримите, учитывая сценарий, который вы иллюстрировали в вопросе.