Как создать пользовательский атрибут AuthorizeAttribute в ASP.NET Core?

Я пытаюсь создать настраиваемый атрибут авторизации в ASP.NET Core. В предыдущих версиях было возможно переопределить bool AuthorizeCore(HttpContextBase httpContext). Но это больше не существует в AuthorizeAttribute.

Каков текущий подход для создания пользовательского атрибута AuthorizeAttribute?

То, что я пытаюсь выполнить: я получаю идентификатор сеанса в разрешении заголовка. Из этого идентификатора я узнаю, действительно ли действительное действие.

Ответ 1

Подход, рекомендованный группой ASP.Net Core, заключается в использовании нового дизайна политики, который полностью задокументирован здесь. Основная идея нового подхода состоит в том, чтобы использовать новый атрибут [Authorize] для обозначения "политики" (например, [Authorize( Policy = "YouNeedToBe18ToDoThis")], где политика зарегистрирована в приложении Startup.cs для выполнения некоторого блока кода (т.е. убедиться, что пользователь имеет выражение о возрасте, когда возраст 18 лет и старше).

Разработка политики является отличным дополнением к этой структуре, и команда разработчиков ASP.Net Security Core заслуживает похвалы за ее внедрение. Тем не менее, это не подходит для всех случаев. Недостатком этого подхода является то, что он не обеспечивает удобного решения для наиболее распространенной необходимости простого утверждения, что для данного контроллера или действия требуется данный тип заявки. В случае, когда приложение может иметь сотни дискретных разрешений, управляющих операциями CRUD на отдельных ресурсах REST ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder" и т.д.), Новый подход требует либо повторяющихся однозначных одно сопоставление между именем политики и именем заявки (например, options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));), или написание некоторого кода для выполнения этих регистраций во время выполнения (например, чтение всех типов заявок из базы данных и выполнение вышеупомянутого вызова в цикле). Проблема с этим подходом в большинстве случаев состоит в том, что он требует ненужных накладных расходов.

Хотя группа ASP.Net Core Security рекомендует никогда не создавать свое собственное решение, в некоторых случаях это может быть наиболее разумным вариантом для начала.

Ниже приведена реализация, которая использует IAuthorizationFilter, чтобы предоставить простой способ выражения требования заявки для данного контроллера или действия:

public class ClaimRequirementAttribute : TypeFilterAttribute
{
    public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
    {
        Arguments = new object[] {new Claim(claimType, claimValue) };
    }
}

public class ClaimRequirementFilter : IAuthorizationFilter
{
    readonly Claim _claim;

    public ClaimRequirementFilter(Claim claim)
    {
        _claim = claim;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
        if (!hasClaim)
        {
            context.Result = new ForbidResult();
        }
    }
}


[Route("api/resource")]
public class MyController : Controller
{
    [ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
    [HttpGet]
    public IActionResult GetResource()
    {
        return Ok();
    }
}

Ответ 2

Я сотрудник службы безопасности asp.net. Во-первых, позвольте мне извиниться за то, что ничего из этого еще не задокументировано за пределами образца музыкального магазина или юнит-тестов, и все это еще дорабатывается с точки зрения открытых API. Подробная документация здесь.

Мы не хотим, чтобы вы писали пользовательские атрибуты авторизации. Если вам нужно сделать это, мы сделали что-то не так. Вместо этого вы должны писать требования авторизации.

Авторизация действует на основе идентификационных данных. Личности создаются путем аутентификации.

В комментариях вы говорите, что хотите проверить идентификатор сессии в заголовке. Ваш идентификатор сессии будет основой для идентификации. Если вы хотите использовать атрибут Authorize, вы должны написать промежуточное программное обеспечение для аутентификации, чтобы взять этот заголовок и превратить его в аутентифицированный ClaimsPrincipal. Затем вы проверите это внутри требования авторизации. Требования к авторизации могут быть настолько сложными, насколько вам нравится, например, здесь, в котором для текущей личности используется требование о дате рождения, и она будет авторизована, если пользователю больше 18 лет;

public class Over18Requirement : AuthorizationHandler<Over18Requirement>, IAuthorizationRequirement
{
        public override void Handle(AuthorizationHandlerContext context, Over18Requirement requirement)
        {
            if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
            {
                context.Fail();
                return;
            }

            var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value);
            int age = DateTime.Today.Year - dateOfBirth.Year;
            if (dateOfBirth > DateTime.Today.AddYears(-age))
            {
                age--;
            }

            if (age >= 18)
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
        }
    }
}

Затем в функции ConfigureServices() вы подключите ее

services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", 
        policy => policy.Requirements.Add(new Authorization.Over18Requirement()));
});

И, наконец, примените его к контроллеру или методу действия с помощью

[Authorize(Policy = "Over18")]

Ответ 3

Похоже, что в ASP.NET Core 2 вы снова можете наследовать AuthorizeAttribute, вам просто нужно также реализовать IAuthorizationFilter (или IAsyncAuthorizationFilter):

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    private readonly string _someFilterParameter;

    public CustomAuthorizeAttribute(string someFilterParameter)
    {
        _someFilterParameter = someFilterParameter;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;

        if (!user.Identity.IsAuthenticated)
        {
            // it isn't needed to set unauthorized result 
            // as the base class already requires the user to be authenticated
            // this also makes redirect to a login page work properly
            // context.Result = new UnauthorizedResult();
            return;
        }

        // you can also use registered services
        var someService = context.HttpContext.RequestServices.GetService<ISomeService>();

        var isAuthorized = someService.IsUserAuthorized(user.Identity.Name, _someFilterParameter);
        if (!isAuthorized)
        {
            context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
            return;
        }
    }
}

Ответ 4

Вы можете создать свой собственный AuthorizationHandler, который найдет пользовательские атрибуты на ваших контроллерах и действиях и передаст их методу HandleRequirementAsync.

public abstract class AttributeAuthorizationHandler<TRequirement, TAttribute> : AuthorizationHandler<TRequirement> where TRequirement : IAuthorizationRequirement where TAttribute : Attribute
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement)
    {
        var attributes = new List<TAttribute>();

        var action = (context.Resource as AuthorizationFilterContext)?.ActionDescriptor as ControllerActionDescriptor;
        if (action != null)
        {
            attributes.AddRange(GetAttributes(action.ControllerTypeInfo.UnderlyingSystemType));
            attributes.AddRange(GetAttributes(action.MethodInfo));
        }

        return HandleRequirementAsync(context, requirement, attributes);
    }

    protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, IEnumerable<TAttribute> attributes);

    private static IEnumerable<TAttribute> GetAttributes(MemberInfo memberInfo)
    {
        return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
    }
}

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

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class PermissionAttribute : AuthorizeAttribute
{
    public string Name { get; }

    public PermissionAttribute(string name) : base("Permission")
    {
        Name = name;
    }
}

Затем создайте требование для добавления в свою политику

public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
    //Add any custom requirement properties if you have them
}

Затем создайте AuthorizationHandler для вашего настраиваемого атрибута, наследуя AttributeAuthorizationHandler, который мы создали ранее. Он будет передан IEnumerable для всех ваших пользовательских атрибутов в методе HandleRequirementsAsync, накопленном из вашего контроллера и действия.

public class PermissionAuthorizationHandler : AttributeAuthorizationHandler<PermissionAuthorizationRequirement, PermissionAttribute>
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement, IEnumerable<PermissionAttribute> attributes)
    {
        foreach (var permissionAttribute in attributes)
        {
            if (!await AuthorizeAsync(context.User, permissionAttribute.Name))
            {
                return;
            }
        }

        context.Succeed(requirement);
    }

    private Task<bool> AuthorizeAsync(ClaimsPrincipal user, string permission)
    {
        //Implement your custom user permission logic here
    }
}

И, наконец, в вашем методе Startup.cs ConfigureServices добавьте свой пользовательский авторизационный сервис в службы и добавьте свою политику.

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

        services.AddAuthorization(options =>
        {
            options.AddPolicy("Permission", policyBuilder =>
            {
                policyBuilder.Requirements.Add(new PermissionAuthorizationRequirement());
            });
        });

Теперь вы можете просто украсить свои контроллеры и действия своим пользовательским атрибутом.

[Permission("AccessCustomers")]
public class CustomersController
{
    [Permission("AddCustomer")]
    IActionResult AddCustomer([FromBody] Customer customer)
    {
        //Add customer
    }
}

Ответ 5

Основываясь на ответе Дерека Грира ОТЛИЧНО, я сделал это с помощью перечислений.

Вот пример моего кода:

public enum PermissionItem
{
    User,
    Product,
    Contact,
    Review,
    Client
}

public enum PermissionAction
{
    Read,
    Create,
}


public class AuthorizeAttribute : TypeFilterAttribute
{
    public AuthorizeAttribute(PermissionItem item, PermissionAction action)
    : base(typeof(AuthorizeActionFilter))
    {
        Arguments = new object[] { item, action };
    }
}

public class AuthorizeActionFilter : IAuthorizationFilter
{
    private readonly PermissionItem _item;
    private readonly PermissionAction _action;
    public AuthorizeActionFilter(PermissionItem item, PermissionAction action)
    {
        _item = item;
        _action = action;
    }
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        bool isAuthorized = MumboJumboFunction(context.HttpContext.User, _item, _action); // :)

        if (!isAuthorized)
        {
            context.Result = new ForbidResult();
        }
    }
}

public class UserController : BaseController
{
    private readonly DbContext _context;

    public UserController( DbContext context) :
        base()
    {
        _logger = logger;
    }

    [Authorize(PermissionItem.User, PermissionAction.Read)]
    public async Task<IActionResult> Index()
    {
        return View(await _context.User.ToListAsync());
    }
}

Ответ 6

  Каков текущий подход к созданию пользовательского AuthorizeAttribute

Легко: не создавайте свой собственный AuthorizeAttribute.

Для сценариев с чистой авторизацией (например, ограничение доступа только для определенных пользователей) рекомендуется использовать новый блок авторизации: https://github.com/aspnet/MusicStore/blob/1c0aeb08bb1ebd846726232226279bbe001782e1/samples/MusicStore/Startup.cs#L84-L92

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<AuthorizationOptions>(options =>
        {
            options.AddPolicy("ManageStore", policy => policy.RequireClaim("Action", "ManageStore"));
        });
    }
}

public class StoreController : Controller
{
    [Authorize(Policy = "ManageStore"), HttpGet]
    public async Task<IActionResult> Manage() { ... }
}

Для аутентификации лучше всего работать на уровне промежуточного программного обеспечения.

Чего именно вы пытаетесь достичь?

Ответ 7

Если кто-либо просто хочет проверить токен на этапе авторизации, используя текущие методы обеспечения безопасности, которые вы можете,

добавьте это в свой Startup/ConfigureServices

    services.AddSingleton<IAuthorizationHandler, BearerAuthorizationHandler>();
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();

    services.AddAuthorization(options => options.AddPolicy("Bearer",
        policy => policy.AddRequirements(new BearerRequirement())
        )
    );

и это в вашей кодовой базе,

public class BearerRequirement : IAuthorizationRequirement
{
    public async Task<bool> IsTokenValid(SomeValidationContext context, string token)
    {
        // here you can check if the token received is valid 
        return true;
    }
}

public class BearerAuthorizationHandler : AuthorizationHandler<BearerRequirement> 
{

    public BearerAuthorizationHandler(SomeValidationContext thatYouCanInject)
    {
       ...
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, BearerRequirement requirement)
    {
        var authFilterCtx = (Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)context.Resource;
        string authHeader = authFilterCtx.HttpContext.Request.Headers["Authorization"];
        if (authHeader != null && authHeader.Contains("Bearer"))
        {
            var token = authHeader.Replace("Bearer ", string.Empty);
            if (await requirement.IsTokenValid(thatYouCanInject, token))
            {
                context.Succeed(requirement);
            }
        }
    }
}

Если код не достигает context.Succeed(...), он все равно не будет выполнен (401).

И тогда в ваших контроллерах вы можете использовать

 [Authorize(Policy = "Bearer", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]