如何在ASP.NET Core中创建自定义AuthorizeAttribute?

本文翻译自:How do you create a custom AuthorizeAttribute in ASP.NET Core?

I'm trying to make a custom authorization attribute in ASP.NET Core. 我正在尝试在ASP.NET Core中创建自定义授权属性。 In previous versions it was possible to override bool AuthorizeCore(HttpContextBase httpContext) . 在以前的版本中,可以重写bool AuthorizeCore(HttpContextBase httpContext) But this no longer exists in AuthorizeAttribute . 但这在AuthorizeAttribute不再存在。

What is the current approach to make a custom AuthorizeAttribute? 制作自定义AuthorizeAttribute的当前方法是什么?

What I am trying to accomplish: I am receiving a session ID in the Header Authorization. 我要完成的工作:我在标题授权中收到一个会话ID。 From that ID I'll know whether a particular action is valid. 通过该ID,我将知道特定操作是否有效。


#1楼

参考:https://stackoom.com/question/281Jf/如何在ASP-NET-Core中创建自定义AuthorizeAttribute


#2楼

What is the current approach to make a custom AuthorizeAttribute 制作自定义AuthorizeAttribute的当前方法是什么

Easy: don't create your own AuthorizeAttribute . 简单:不要创建自己的AuthorizeAttribute

For pure authorization scenarios (like restricting access to specific users only), the recommended approach is to use the new authorization block: https://github.com/aspnet/MusicStore/blob/1c0aeb08bb1ebd846726232226279bbe001782e1/samples/MusicStore/Startup.cs#L84-L92 对于纯授权方案(如仅限制对特定用户的访问),建议的方法是使用新的授权块: https : //github.com/aspnet/MusicStore/blob/1c0aeb08bb1ebd846726232226279bbe001782e1/samples/MusicStore/Startup.cs#L84 -L92

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

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

For authentication, it's best handled at the middleware level. 对于身份验证,最好在中间件级别进行处理。

What are you trying to achieve exactly? 您到底想达到什么目标?


#3楼

I'm the asp.net security person. 我是asp.net安全人员。 Firstly let me apologize that none of this is documented yet outside of the music store sample or unit tests, and it's all still being refined in terms of exposed APIs. 首先,我很抱歉,除了音乐商店样本或单元测试之外,没有任何文档被记录下来,并且仍然在公开的API方面对其进行了完善。 Detailed documentation is here . 详细的文档在这里 。

We don't want you writing custom authorize attributes. 我们不希望您编写自定义授权属性。 If you need to do that we've done something wrong. 如果您需要这样做,我们做错了。 Instead, you should be writing authorization requirements . 相反,您应该编写授权要求

Authorization acts upon Identities. 授权作用于身份。 Identities are created by authentication. 身份是通过身份验证创建的。

You say in comments you want to check a session ID in a header. 您在注释中说,您想检查标题中的会话ID。 Your session ID would be the basis for identity. 您的会话ID将成为身份的基础。 If you wanted to use the Authorize attribute you'd write an authentication middleware to take that header and turn it into an authenticated ClaimsPrincipal . 如果您想使用Authorize属性,则可以编写身份验证中间件来获取该标头并将其转换为经过身份验证的ClaimsPrincipal You would then check that inside an authorization requirement. 然后,您将在授权要求中进行检查。 Authorization requirements can be as complicated as you like, for example here's one that takes a date of birth claim on the current identity and will authorize if the user is over 18; 授权要求可以任意复杂,例如,这里要求以当前身份声明出生日期,并在用户超过18岁时进行授权;

public class Over18Requirement : AuthorizationHandler, 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();
            }
        }
    }
}

Then in your ConfigureServices() function you'd wire it up 然后在您的ConfigureServices()函数中将其连接起来

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

And finally, apply it to a controller or action method with 最后,通过以下方式将其应用于控制器或操作方法

[Authorize(Policy = "Over18")]

#4楼

You can create your own AuthorizationHandler that will find custom attributes on your Controllers and Actions, and pass them to the HandleRequirementAsync method. 您可以创建自己的AuthorizationHandler,以在Controllers和Actions上找到自定义属性,并将它们传递给HandleRequirementAsync方法。

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

        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 attributes);

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

Then you can use it for any custom attributes you need on your controllers or actions. 然后,您可以将其用于控制​​器或操作上所需的任何自定义属性。 For example to add permission requirements. 例如添加权限要求。 Just create your custom attribute. 只需创建您的自定义属性。

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

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

Then create a Requirement to add to your Policy 然后创建一个要求以添加到您的策略中

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

Then create the AuthorizationHandler for your custom attribute, inheriting the AttributeAuthorizationHandler that we created earlier. 然后,为您的自定义属性创建AuthorizationHandler,并继承我们先前创建的AttributeAuthorizationHandler。 It will be passed an IEnumerable for all your custom attributes in the HandleRequirementsAsync method, accumulated from your Controller and Action. 将为HandleRequirementsAsync方法中的所有自定义属性传递一个IEnumerable,该IEnumerable是从Controller和Action累积的。

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

        context.Succeed(requirement);
    }

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

And finally, in your Startup.cs ConfigureServices method, add your custom AuthorizationHandler to the services, and add your Policy. 最后,在Startup.cs ConfigureServices方法中,将自定义AuthorizationHandler添加到服务中,然后添加Policy。

        services.AddSingleton();

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

Now you can simply decorate your Controllers and Actions with your custom attribute. 现在,您可以使用自定义属性简单地装饰控制器和动作。

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

#5楼

The approach recommended by the ASP.Net Core team is to use the new policy design which is fully documented here . ASP.Net Core团队推荐的方法是使用新的策略设计, 在此将其详细记录。 The basic idea behind the new approach is to use the new [Authorize] attribute to designate a "policy" (eg [Authorize( Policy = "YouNeedToBe18ToDoThis")] where the policy is registered in the application's Startup.cs to execute some block of code (ie ensure the user has an age claim where the age is 18 or older). 新方法的基本思想是使用新的[Authorize]属性来指定一个“策略”(例如[Authorize( Policy = "YouNeedToBe18ToDoThis")] ,该策略已在应用程序的Startup.cs中注册以执行某些代码(即确保用户具有18岁或18岁以上的年龄声明)。

The policy design is a great addition to the framework and the ASP.Net Security Core team should be commended for its introduction. 策略设计是对框架的重要补充,ASP.Net Security Core团队的引入值得赞扬。 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 (eg options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder)); ), or writing some code to perform these registrations at run time (eg 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. 如果应用程序可能具有数百个离散权限来管理单个REST资源(“ CanCreateOrder”,“ CanReadOrder”,“ CanUpdateOrder”,“ CanDeleteOrder”等)上的CRUD操作,则新方法要么需要重复的一对一操作,要么一种策略名称与声明名称之间的映射(例如options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder)); ),或编写一些代码以在运行时执行这些注册(例如,从数据库中读取所有索赔类型,然后循环执行上述调用。)在大多数情况下,这种方法的问题是不必要的开销。

While the ASP.Net Core Security team recommends never creating your own solution, in some cases this may be the most prudent option with which to start. 尽管ASP.Net Core Security团队建议不要创建自己的解决方案,但在某些情况下,这可能是最明智的选择。

The following is an implementation which uses the IAuthorizationFilter to provide a simple way to express a claim requirement for a given controller or action: 以下是使用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();
    }
}

#6楼

Based on Derek Greer GREAT answer, i did it with enums. 基于Derek Greer GREAT的答案,我使用枚举来做到这一点。

Here is an example of my code: 这是我的代码示例:

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 Index()
    {
        return View(await _context.User.ToListAsync());
    }
}

你可能感兴趣的:(c#,asp.net,authorization,asp.net-core)