关于c#:如何在ASP.NET Core中创建自定义AuthorizeAttribute?

How do you create a custom AuthorizeAttribute in ASP.NET Core?

我正在尝试在ASP.NET Core中创建自定义授权属性。 在以前的版本中,可以覆盖bool AuthorizeCore(HttpContextBase httpContext)。 但是AuthorizeAttribute中不再存在这种情况。

制作自定义AuthorizeAttribute的当前方法是什么?

我想要完成的任务:我在Header Authorization中收到一个会话ID。 从该ID我将知道特定动作是否有效。


ASP.Net核心团队建议的方法是使用新的策略设计,此处已完整记录。新方法背后的基本思想是使用新的[Authorize]属性来指定"策略"(例如[Authorize( Policy ="YouNeedToBe18ToDoThis")],其中策略在应用程序的Startup.cs中注册以执行某些代码块(即确保用户具有年龄在18岁或以上的年龄申请表。

策略设计是框架的一个很好的补充,ASP.Net安全核心团队的引入应该受到赞扬。也就是说,它并不适合所有情况。这种方法的缺点在于它无法为最简单地断言给定控制器或动作需要给定声明类型的最常见需求提供方便的解决方案。如果应用程序可能具有数百个离散权限来管理各个REST资源上的CRUD操作("CanCreateOrder","CanReadOrder","CanUpdateOrder","CanDeleteOrder"等),则新方法要么重复一次到一次 - 策略名称和声明名称之间的一个映射(例如options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission,"CanUpdateOrder));),或编写一些代码以在运行时执行这些注册(例如,从数据库中读取所有声明类型并在循环中执行上述调用)。对于大多数情况,这种方法的问题在于它是不必要的开销。

虽然ASP.Net核心安全团队建议永远不要创建自己的解决方案,但在某些情况下,这可能是最谨慎的选择。

以下是使用IAuthorizationFilter提供表达给定控制器或操作的声明要求的简单方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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安全人员。 首先让我道歉,在音乐商店样本或单元测试之外,这些都没有被记录下来,并且它们仍然在暴露的API方面进行了改进。详细的文档在这里。

我们不希望您编写自定义授权属性。如果你需要这样做我们做错了什么。相反,你应该写授权要求。

授权作用于身份。身份通过身份验证创建。

您在评论中说要检查标题中的会话ID。您的会话ID将成为身份的基础。如果您想使用Authorize属性,您需要编写一个身份验证中间件来获取该标头并将其转换为经过身份验证的ClaimsPrincipal。然后,您将在授权要求中检查该内容。授权要求可以像您一样复杂,例如,这是一个对当前身份提出出生日期索赔的要求,并且如果用户超过18岁则会授权;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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()功能中,你将它连接起来

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

最后将其应用于控制器或操作方法

1
[Authorize(Policy ="Over18")]


看来,使用ASP.NET Core 2,您可以再次继承AuthorizeAttribute,您只需要实现IAuthorizationFilter(或IAsyncAuthorizationFilter):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[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;
        }
    }
}


What is the current approach to make a custom AuthorizeAttribute

简单:不要创建自己的AuthorizeAttribute

对于纯授权方案(如仅限制对特定用户的访问),建议的方法是使用新的授权块:https://github.com/aspnet/MusicStore/blob/1c0aeb08bb1ebd846726232226279bbe001782e1/samples/MusicStore/Startup.cs#L84 -L92

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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,它将在Controllers和Actions上找到自定义属性,并将它们传递给HandleRequirementAsync方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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>();
    }
}

然后,您可以将其用于控制??器或操作所需的任何自定义属性。例如,添加权限要求。只需创建自定义属性即可。

1
2
3
4
5
6
7
8
9
10
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class PermissionAttribute : AuthorizeAttribute
{
    public string Name { get; }

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

然后创建要添加到策略的要求

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

然后为自定义属性创建AuthorizationHandler,继承我们之前创建的AttributeAuthorizationHandler。它将通过一个IEnumerable传递给HandleRequirementsAsync方法中的所有自定义属性,这些属性是从Controller和Action累积的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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添加到服务中,然后添加策略。

1
2
3
4
5
6
7
8
9
        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

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

现在,您可以使用自定义属性简单地修饰控制器和操作。

1
2
3
4
5
6
7
8
9
[Permission("AccessCustomers")]
public class CustomersController
{
    [Permission("AddCustomer")]
    IActionResult AddCustomer([FromBody] Customer customer)
    {
        //Add customer
    }
}


根据Derek Greer的回答,我用枚举做了。

这是我的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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());
    }
}