Как создать пользовательский атрибут AuthorizeAttribute в ASP.NET ядро?
Я пытаюсь сделать пользовательский атрибут авторизации в ASP.NET ядро. В предыдущих версиях можно было переопределить bool AuthorizeCore(HttpContextBase httpContext). Но это больше не существует в AuthorizeAttribute.
каков текущий подход к созданию пользовательского атрибута AuthorizeAttribute?
что я пытаюсь сделать: я получаю идентификатор сеанса в заголовке авторизации. Из этого идентификатора я буду знать, действительно ли определенное действие.
6 ответов:
подход, рекомендованный ASP.Net основная команда должна использовать новый дизайн политики, который полностью документирован здесь. Основная идея нового подхода заключается в использовании нового атрибута [Authorize] для обозначения "политики" (например,
[Authorize( Policy = "YouNeedToBe18ToDoThis")]где политика зарегистрирована при запуске приложения.cs для выполнения некоторого блока кода (т. е. убедитесь, что у пользователя есть возрастное требование, где возраст 18 или старше).дизайн политики является отличным дополнением к структуре и the ASP.Net следует воздать должное основной группе по вопросам безопасности за ее введение. Тем не менее, это не очень хорошо подходит для всех случаев. Недостатком этого подхода является то, что он не может обеспечить удобное решение для наиболее распространенной потребности просто утверждать, что данный контроллер или действие требует данного типа утверждения. В случае, когда приложение может иметь сотни дискретных разрешений, управляющих операциями CRUD на отдельных ресурсах REST ("CanCreateOrder", "CanReadOrder", " CanUpdateOrder", "CanDeleteOrder" и др.), новый подход либо требует повторяющихся взаимно однозначных сопоставлений между именем политики и именем утверждения (например,
options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));), или написать некоторый код для выполнения этих регистраций во время выполнения (например, прочитать все типы утверждений из базы данных и выполнить вышеупомянутый вызов в цикле). Проблема с этим подходом в большинстве случаев заключается в том, что это ненужные накладные расходы.В то время как ASP.Net основная группа безопасности рекомендует никогда не создавать собственное решение, в в некоторых случаях это может быть наиболее разумным вариантом, с какой начать.
ниже приведена реализация, которая использует 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 ядро 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() { ... } }для проверки подлинности, это лучше всего решать на уровне промежуточного программного обеспечения.
чего именно вы пытаетесь достичь?
вы можете создать свой собственный 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 } }и, наконец, в свой стартап.метод 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()); } }
Comments