我试图使一个自定义授权属性在ASP。净的核心。在以前的版本中,可以重写bool AuthorizeCore(HttpContextBase httpContext)。但是这在AuthorizeAttribute中不再存在。
当前制作自定义AuthorizeAttribute的方法是什么?
我想要完成的:我正在头授权中接收会话ID。通过该ID,我将知道特定操作是否有效。
我试图使一个自定义授权属性在ASP。净的核心。在以前的版本中,可以重写bool AuthorizeCore(HttpContextBase httpContext)。但是这在AuthorizeAttribute中不再存在。
当前制作自定义AuthorizeAttribute的方法是什么?
我想要完成的:我正在头授权中接收会话ID。通过该ID,我将知道特定操作是否有效。
当前回答
ASP。Net Core团队将使用这里完整记录的新策略设计。新方法背后的基本思想是使用新的[Authorize]属性来指定一个“策略”(例如[Authorize(policy = "YouNeedToBe18ToDoThis")],其中策略在应用程序的Startup.cs中注册,以执行一些代码块(即确保用户在年龄为18岁或以上时有年龄声明)。
The policy design is a great addition to the framework and the ASP.Net Security Core team should be commended for its introduction. That said, it isn't well-suited for all cases. The shortcoming of this approach is that it fails to provide a convenient solution for the most common need of simply asserting that a given controller or action requires a given claim type. In the case where an application may have hundreds of discrete permissions governing CRUD operations on individual REST resources ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder", etc.), the new approach either requires repetitive one-to-one mappings between a policy name and a claim name (e.g. options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));), or writing some code to perform these registrations at run time (e.g. read all claim types from a database and perform the aforementioned call in a loop). The problem with this approach for the majority of cases is that it's unnecessary overhead.
而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();
}
}
其他回答
只是给@Shawn的回答补充了一点。如果你正在使用dotnet 5,你需要更新类为:
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>();
if (context.Resource is HttpContext httpContext)
{
var endPoint = httpContext.GetEndpoint();
var action = endPoint?.Metadata.GetMetadata<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) => memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
}
注意获取ControllerActionDescriptor的方式已经改变。
您可以创建自己的AuthorizationHandler,它将在控制器和动作上找到自定义属性,并将它们传递给handlerrequirementasync方法。
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,用于handlerrequirementsasync方法中的所有自定义属性,这些属性是由控制器和动作积累而来的。
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添加到服务中,并添加您的Policy。
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
}
}
这里很多人已经说过了,但是有了策略处理程序,你就可以在。net框架中使用旧方法实现的功能而言,你可以走得更远。
我在SO网站上快速写了一个答案:https://stackoverflow.com/a/61963465/7081176 对我来说,在制作了一些类后,它完美地工作了:
EditUserRequirement:
public class EditUserRequirement : IAuthorizationRequirement
{
public EditUserRequirement()
{
}
}
一个抽象处理程序,使我的生活更容易:
public abstract class AbstractRequirementHandler<T> : IAuthorizationHandler
where T : IAuthorizationRequirement
{
public async Task HandleAsync(AuthorizationHandlerContext context)
{
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
if (requirement is T typedRequirement)
{
await HandleRequirementAsync(context, typedRequirement);
}
}
}
protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, T requirement);
}
抽象处理程序的实现:
public class EditUserRequirementHandler : AbstractRequirementHandler<EditUserRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EditUserRequirement requirement)
{
// If the user is owner of the resource, allow it.
if (IsOwner(context.User, g))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
private static bool IsOwner(ClaimsPrincipal user, Guid userIdentifier)
{
return user.GetUserIdentifier() == userIdentifier;
}
}
注册我的处理程序和需求: 服务。AddSingleton < IAuthorizationHandler, EditUserRequirementHandler > ();
services.AddAuthorization(options =>
{
options.AddPolicy(Policies.Policies.EditUser, policy =>
{
policy.Requirements.Add(new EditUserRequirement());
});
});
然后使用我在Blazor的策略:
<AuthorizeView Policy="@Policies.EditUser" Resource="@id">
<NotAuthorized>
<Unauthorized />
</NotAuthorized>
<Authorized Context="Auth">
...
</Authorized>
</AuthorizeView>
我希望这对面临这个问题的人有用。
似乎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;
}
}
}
基于Derek Greer伟大的答案,我用枚举来做。
下面是我的代码示例:
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());
}
}