你如何在ASP.NET Core中创建一个自定义的AuthorizeAttribute?
我正在尝试在ASP.NET Core中创建一个自定义授权属性。 在以前的版本中,可以重写bool AuthorizeCore(HttpContextBase httpContext)
。 但是这在AuthorizeAttribute
不再存在。
什么是目前做一个自定义的AuthorizeAttribute的方法?
我正在尝试完成:我正在接收标题授权中的会话ID。 从这个ID我会知道一个特定的行动是否有效。
我是asp.net安全人员。 首先让我道歉的是,这些都没有记录在音乐库样本或单元测试之外,而且这些样本都依照暴露的API进行了改进。 详细的文档在这里 。
我们不希望你写自定义的授权属性。 如果你需要这样做,我们做错了。 相反,你应该写授权要求 。
授权对身份起作用。 身份通过身份验证创建。
你在评论中说你想检查标题中的会话ID。 您的会话ID将是身份的基础。 如果你想使用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核心团队推荐的方法是使用这里完整记录的新策略设计。 新方法背后的基本思想是使用新的[Authorize]属性来指定策略在应用程序的Startup.cs中注册的一个“策略”(例如[Authorize( Policy = "YouNeedToBe18ToDoThis")]
代码(即确保用户有年龄在18岁以上的年龄要求)。
然而,这种方法的缺点在于它没有提供一种简单的解决方案来满足最简单的要求:一个给定的控制器或动作需要给定的索赔类型。 如果应用程序可能拥有数百个独立的权限来管理单个REST资源上的CRUD操作(“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(); } }
什么是目前的方法来做一个自定义的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方法中的所有自定义属性,从Controller和Action中累积。
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方法中,将您的自定义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()); } }