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

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

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

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

6 ответов


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

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

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

следующая реализация, которая использует 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();
    }
}

Я asp.net охранник. во-первых, позвольте мне извиниться, что ничто из этого не задокументировано еще вне образца musicstore или модульных тестов, и все это все еще уточняется с точки зрения открытых 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")]

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

[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;
        }
    }
}

каков текущий подход к созданию пользовательского 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() { ... }
}

для проверки подлинности, он лучше всего обрабатывается на уровне middleware.

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


вы можете создать свой собственный 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
    }
}

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

        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
    }
}

на основе Дерека Грира отличный ответ, я сделал это с перечислениями.

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

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 : IAsyncActionFilter
{
    private readonly PermissionItem _item;
    private readonly PermissionAction _action;
    public AuthorizeActionFilter(PermissionItem item, PermissionAction action)
    {
        _item = item;
        _action = action;
    }
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        bool isAuthorized = MumboJumboFunction(context.HttpContext.User, _item, _action); // :)

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

        }
        else
        {
            await next();
        }
    }
}

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());
    }
}