如何在 ASP.NET 核心中创建自定义授权属性

本文关键字:创建 自定义 授权 属性 核心 ASP NET | 更新日期: 2023-09-27 18:03:19

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

当前创建自定义授权属性的方法是什么?

我正在尝试完成什么:我在标头授权中收到会话 ID。通过该 ID,我将知道特定操作是否有效。

如何在 ASP.NET 核心中创建自定义授权属性

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 提供一种简单的方法来表达给定控制器或操作的声明要求:

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 岁,则会授权;

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 dobVal = context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value;
    var dateOfBirth = Convert.ToDateTime(dobVal);
    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 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;
        }
    }
}

基于德里克·格里尔的伟大答案,我用枚举做到了。

这是我的代码示例:

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());
    }
}

您可以创建自己的 AuthorizationHandler,该处理程序将查找控制器和操作上的自定义属性,并将它们传递给 HandleRequirementsAsync 方法。

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。它将为HandleRequirementsAsync方法中的所有自定义属性传递一个IEnumerable,这些属性是从控制器和操作中累积的。

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 方法中,将自定义授权处理程序添加到服务,并添加策略。

        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
    }
}

什么?!

我决定添加另一个简单的答案。B/c我发现这些答案中的大多数都有点过度设计。也因为我需要一种方法来授予授权,而不仅仅是拒绝它。这里的大多数答案都提供了一种"加强"安全性的方法,但我想"放松"它。例如:"如果配置了某些应用程序设置,则允许匿名用户访问"。

public class MyAuthAttribute : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        //check access 
        if (CheckPermissions())
        {
            //all good, add optional code if you want. Or don't
        }
        else
        {
            //DENIED!
            //return "ChallengeResult" to redirect to login page (for example)
            context.Result = new ChallengeResult(CookieAuthenticationDefaults.AuthenticationScheme);
        }
    }
}

就是这样。无需弄乱"策略","声明","处理程序"和其他[哔哔声]

用法:

// GET api/Get/5
[MyAuth]
public ActionResult<string> Get(int id)
{
    return "blahblah";
}

当前创建自定义授权属性的方法是什么

对于纯授权方案(例如仅限制特定用户的访问(,建议的方法是使用新的授权块: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() { ... }
}

对于身份验证,最好在中间件级别处理。

你到底想实现什么?

现代方式是 AuthenticationHandlers

在启动中.cs添加

services.AddAuthentication("BasicAuthentication").AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private readonly IUserService _userService;
        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock,
            IUserService userService)
            : base(options, logger, encoder, clock)
        {
            _userService = userService;
        }
        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey("Authorization"))
                return AuthenticateResult.Fail("Missing Authorization Header");
            User user = null;
            try
            {
                var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
                var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
                var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
                var username = credentials[0];
                var password = credentials[1];
                user = await _userService.Authenticate(username, password);
            }
            catch
            {
                return AuthenticateResult.Fail("Invalid Authorization Header");
            }
            if (user == null)
                return AuthenticateResult.Fail("Invalid User-name or Password");
            var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            };
            var identity = new ClaimsIdentity(claims, Scheme.Name);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, Scheme.Name);
            return AuthenticateResult.Success(ticket);
        }
    }

IUserService是您在其中拥有用户名和密码的情况下提供的服务。基本上,它返回一个用于映射声明的用户类。

var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            }; 

然后你可以查询这些声明和她你映射的任何数据,这些数据相当多,看看 ClaimTypes 类

您可以在扩展方法中使用它来获取任何映射

public int? GetUserId()
{
   if (context.User.Identity.IsAuthenticated)
    {
       var id=context.User.FindFirst(ClaimTypes.NameIdentifier);
       if (!(id is null) && int.TryParse(id.Value, out var userId))
            return userId;
     }
      return new Nullable<int>();
 }

这种新方法,我认为比这里显示的旧方法更好,两者都有效

public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (actionContext.Request.Headers.Authorization != null)
        {
            var authToken = actionContext.Request.Headers.Authorization.Parameter;
            // decoding authToken we get decode value in 'Username:Password' format
            var decodeauthToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(authToken));
            // spliting decodeauthToken using ':'
            var arrUserNameandPassword = decodeauthToken.Split(':');
            // at 0th postion of array we get username and at 1st we get password
            if (IsAuthorizedUser(arrUserNameandPassword[0], arrUserNameandPassword[1]))
            {
                // setting current principle
                Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(arrUserNameandPassword[0]), null);
            }
            else
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            }
        }
        else
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        }
    }
    public static bool IsAuthorizedUser(string Username, string Password)
    {
        // In this method we can handle our database logic here...
        return Username.Equals("test") && Password == "test";
    }
}

如果有人只想使用当前的安全实践在授权阶段验证持有者令牌,您可以,

将其添加到您的启动/配置服务

    services.AddSingleton<IAuthorizationHandler, BearerAuthorizationHandler>();
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();
    services.AddAuthorization(options => options.AddPolicy("Bearer",
        policy => policy.AddRequirements(new BearerRequirement())
        )
    );

这在你的代码库中,

public class BearerRequirement : IAuthorizationRequirement
{
    public async Task<bool> IsTokenValid(SomeValidationContext context, string token)
    {
        // here you can check if the token received is valid 
        return true;
    }
}
public class BearerAuthorizationHandler : AuthorizationHandler<BearerRequirement> 
{
    public BearerAuthorizationHandler(SomeValidationContext thatYouCanInject)
    {
       ...
    }
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, BearerRequirement requirement)
    {
        var authFilterCtx = (Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)context.Resource;
        string authHeader = authFilterCtx.HttpContext.Request.Headers["Authorization"];
        if (authHeader != null && authHeader.Contains("Bearer"))
        {
            var token = authHeader.Replace("Bearer ", string.Empty);
            if (await requirement.IsTokenValid(thatYouCanInject, token))
            {
                context.Succeed(requirement);
            }
        }
    }
}

如果代码未达到 context.Succeed(...)则无论如何都会失败 (401(。

然后在您的控制器中您可以使用

 [Authorize(Policy = "Bearer", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

下面的代码在.Net Core 5中对我有用

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AccessAuthorizationAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    public string Module { get; set; } //Permission string to get from controller
    public AccessAuthorizationAttribute(string module)
    {
        Module = module;
    }
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        //Validate if any permissions are passed when using attribute at controller or action level
        if (string.IsNullOrEmpty(Module))
        {
            //Validation cannot take place without any permissions so returning unauthorized
            context.Result = new UnauthorizedResult();
            return;
        }
       
        if (hasAccess)
        {
            return;
        }
        context.Result = new UnauthorizedResult();
        return;
    }
}

接受的答案(https://stackoverflow.com/a/41348219/4974715(在现实中是不可维护或合适的,因为"CanReadResource"被用作索赔(但本质上应该是现实中的政策,IMO(。答案中的方法在它的使用方式上是不行的,因为如果一个操作方法需要许多不同的声明设置,那么有了这个答案,你将不得不反复编写类似......

[ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")] 
[ClaimRequirement(MyClaimTypes.AnotherPermision, "AnotherClaimVaue")]
//and etc. on a single action.

所以,想象一下这需要多少编码。理想情况下,"CanReadResource"应该是一个策略,它使用许多声明来确定用户是否可以读取资源。

我所做的是将我的策略创建为枚举,然后循环并设置要求,如下所示......

services.AddAuthorization(authorizationOptions =>
        {
            foreach (var policyString in Enum.GetNames(typeof(Enumerations.Security.Policy)))
            {
                authorizationOptions.AddPolicy(
                    policyString,
                    authorizationPolicyBuilder => authorizationPolicyBuilder.Requirements.Add(new DefaultAuthorizationRequirement((Enumerations.Security.Policy)Enum.Parse(typeof(Enumerations.Security.Policy), policyWrtString), DateTime.UtcNow)));
      /* Note that thisn does not stop you from 
          configuring policies directly against a username, claims, roles, etc. You can do the usual.
     */
            }
        }); 

类看起来像...

public class DefaultAuthorizationRequirement : IAuthorizationRequirement
{
    public Enumerations.Security.Policy Policy {get; set;} //This is a mere enumeration whose code is not shown.
    public DateTime DateTimeOfSetup {get; set;} //Just in case you have to know when the app started up. And you may want to log out a user if their profile was modified after this date-time, etc.
}
public class DefaultAuthorizationHandler : AuthorizationHandler<DefaultAuthorizationRequirement>
{
    private IAServiceToUse _aServiceToUse;
    public DefaultAuthorizationHandler(
        IAServiceToUse aServiceToUse
        )
    {
        _aServiceToUse = aServiceToUse;
    }
    protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, DefaultAuthorizationRequirement requirement)
    {
        /*Here, you can quickly check a data source or Web API or etc. 
           to know the latest date-time of the user's profile modification...
        */
        if (_aServiceToUse.GetDateTimeOfLatestUserProfileModication > requirement.DateTimeOfSetup)
        {
            context.Fail(); /*Because any modifications to user information, 
            e.g. if the user used another browser or if by Admin modification, 
            the claims of the user in this session cannot be guaranteed to be reliable.
            */
            return;
        }
        bool shouldSucceed = false; //This should first be false, because context.Succeed(...) has to only be called if the requirement specifically succeeds.
        bool shouldFail = false; /*This should first be false, because context.Fail() 
        doesn't have to be called if there's no security breach.
        */
        // You can do anything.
        await doAnythingAsync();
       /*You can get the user's claims... 
          ALSO, note that if you have a way to priorly map users or users with certain claims 
          to particular policies, add those policies as claims of the user for the sake of ease. 
          BUT policies that require dynamic code (e.g. checking for age range) would have to be 
          coded in the switch-case below to determine stuff.
       */
        var claims = context.User.Claims;
        // You can, of course, get the policy that was hit...
        var policy = requirement.Policy
        //You can use a switch case to determine what policy to deal with here...
        switch (policy)
        {
            case Enumerations.Security.Policy.CanReadResource:
                 /*Do stuff with the claims and change the 
                     value of shouldSucceed and/or shouldFail.
                */
                 break;
            case Enumerations.Security.Policy.AnotherPolicy:
                 /*Do stuff with the claims and change the 
                    value of shouldSucceed and/or shouldFail.
                 */
                 break;
                // Other policies too.
            default:
                 throw new NotImplementedException();
        }
        /* Note that the following conditions are 
            so because failure and success in a requirement handler 
            are not mutually exclusive. They demand certainty.
        */
        if (shouldFail)
        {
            context.Fail(); /*Check the docs on this method to 
            see its implications.
            */
        }                
        if (shouldSucceed)
        {
            context.Succeed(requirement); 
        } 
     }
}

请注意,上面的代码还可以启用用户到数据存储中的策略的预映射。因此,在为用户撰写声明时,您基本上会检索直接或间接预先映射到用户的策略(例如,因为用户具有特定的声明值,并且该声明值已被识别并映射到策略,因此它也为具有该声明值的用户提供自动映射(, 并将策略登记为声明,以便在授权处理程序中,您只需检查用户的声明是否包含要求。保单作为其索赔中索赔项的值。这是为了满足政策要求的静态方式,例如"名字"要求本质上是相当静态的。因此,对于上面的示例(我忘记在我之前对此答案的更新中提供有关授权属性的示例(,使用带有授权属性的策略如下所示,其中 ViewRecord 是枚举成员:

[Authorize(Policy = nameof(Enumerations.Security.Policy.ViewRecord))] 

动态要求可以是检查年龄范围等,使用此类要求的策略不能预先映射到用户。

动态保单声明检查(例如,检查用户是否年满 18 岁(的一个例子已经在 @blowdart (https://stackoverflow.com/a/31465227/4974715( 给出的答案中。

PS:我在手机上输入了这个。请原谅任何拼写错误和缺少格式。

在撰写本文时,我相信这可以通过 asp.net 核心 2 及以上版本的 IClaimsTransformation 接口来实现。 我刚刚实现了一个概念验证,该概念证明足以共享在这里发布。

public class PrivilegesToClaimsTransformer : IClaimsTransformation
{
    private readonly IPrivilegeProvider privilegeProvider;
    public const string DidItClaim = "http://foo.bar/privileges/resolved";
    public PrivilegesToClaimsTransformer(IPrivilegeProvider privilegeProvider)
    {
        this.privilegeProvider = privilegeProvider;
    }
    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        if (principal.Identity is ClaimsIdentity claimer)
        {
            if (claimer.HasClaim(DidItClaim, bool.TrueString))
            {
                return principal;
            }
            var privileges = await this.privilegeProvider.GetPrivileges( ... );
            claimer.AddClaim(new Claim(DidItClaim, bool.TrueString));
            foreach (var privilegeAsRole in privileges)
            {
                claimer.AddClaim(new Claim(ClaimTypes.Role /*"http://schemas.microsoft.com/ws/2008/06/identity/claims/role" */, privilegeAsRole));
            }
        }
        return principal;
    }
}

要在控制器中使用它,只需向方法添加适当的[Authorize(Roles="whatever")]即可。

[HttpGet]
[Route("poc")]
[Authorize(Roles = "plugh,blast")]
public JsonResult PocAuthorization()
{
    var result = Json(new
    {
        when = DateTime.UtcNow,
    });
    result.StatusCode = (int)HttpStatusCode.OK;
    return result;
}

在我们的例子中,每个请求都包含一个作为 JWT 的授权标头。 这是原型,我相信下周我们将在我们的生产系统中做一些非常接近它的事情。

未来的选民,考虑投票时的写作日期。 截至今天,本works on my machine. ™ 您可能希望在实现上进行更多的错误处理和日志记录。

这是一个简单的 5 步指南,介绍如何使用策略为所有复制和粘贴器实现自定义角色授权:)。我使用了这些文档。

创建要求:

public class RoleRequirement : IAuthorizationRequirement
{
    public string Role { get; set; }
}

创建处理程序:

public class RoleHandler : AuthorizationHandler<RoleRequirement>
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
    {
        var requiredRole = requirement.Role;
        //custom auth logic
        //  you can use context to access authenticated user,
        //  you can use dependecy injection to call custom services 
        var hasRole = true;
        if (hasRole)
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail(new AuthorizationFailureReason(this, $"Role {requirement.Role} missing"));
        }
    }
}

在程序.cs中添加处理程序:

builder.Services.AddSingleton<IAuthorizationHandler, RoleHandler>();

在程序中添加包含角色要求的策略.cs:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Read", policy => policy.Requirements.Add(new RoleRequirement{Role = "ReadAccess_Custom_System"}));
});

使用您的策略:

[Authorize("Read")]
public class ExampleController : ControllerBase
{
}

只是增加了@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>();
}

注意到获取控制器操作描述符的方式已更改。

我有持有者令牌,我可以读取声明。我在控制器和操作上使用该属性

public class CustomAuthorizationAttribute : ActionFilterAttribute
{
    public string[] Claims;
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // check user 
        var contextUser = context?.HttpContext?.User;
        if (contextUser == null)
        {
            throw new BusinessException("Forbidden");
        }

        // check roles
        var roles = contextUser.FindAll("http://schemas.microsoft.com/ws/2008/06/identity/claims/role").Select(c => c.Value).ToList();
        if (!roles.Any(s => Claims.Contains(s)))
        {
            throw new BusinessException("Forbidden");
        }
        base.OnActionExecuting(context);
    }
}

[CustomAuthorization(Claims = new string[]
    {
        nameof(AuthorizationRole.HR_ADMIN),
        nameof(AuthorizationRole.HR_SETTING)
    })]
[Route("api/[controller]")]
[ApiController]
public class SomeAdminController : ControllerBase
{
    private readonly IMediator _mediator;
    public SomeAdminController(IMediator mediator)
    {
        _mediator = mediator;
    }
    [HttpGet("list/SomeList")]
    public async Task<IActionResult> SomeList()
        => Ok(await _mediator.Send(new SomeListQuery()));
}

那就是角色

public struct AuthorizationRole
{
    public static string HR_ADMIN;
    public static string HR_SETTING;
}

在我们的应用程序中进行授权。我们必须根据授权属性中传递的参数调用服务。

例如,如果我们想检查登录的医生是否可以查看患者预约,我们会将"View_Appointment"传递给自定义授权属性,并在DB服务中检查该权限,并根据结果进行检测。下面是此方案的代码:

    public class PatientAuthorizeAttribute : TypeFilterAttribute
    {
    public PatientAuthorizeAttribute(params PatientAccessRights[] right) : base(typeof(AuthFilter)) //PatientAccessRights is an enum
    {
        Arguments = new object[] { right };
    }
    private class AuthFilter : IActionFilter
    {
        PatientAccessRights[] right;
        IAuthService authService;
        public AuthFilter(IAuthService authService, PatientAccessRights[] right)
        {
            this.right = right;
            this.authService = authService;
        }
        public void OnActionExecuted(ActionExecutedContext context)
        {
        }
        public void OnActionExecuting(ActionExecutingContext context)
        {
            var allparameters = context.ActionArguments.Values;
            if (allparameters.Count() == 1)
            {
                var param = allparameters.First();
                if (typeof(IPatientRequest).IsAssignableFrom(param.GetType()))
                {
                    IPatientRequest patientRequestInfo = (IPatientRequest)param;
                    PatientAccessRequest userAccessRequest = new PatientAccessRequest();
                    userAccessRequest.Rights = right;
                    userAccessRequest.MemberID = patientRequestInfo.PatientID;
                    var result = authService.CheckUserPatientAccess(userAccessRequest).Result; //this calls DB service to check from DB
                    if (result.Status == ReturnType.Failure)
                    {
                        //TODO: return apirepsonse
                        context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
                    }
                }
                else
                {
                    throw new AppSystemException("PatientAuthorizeAttribute not supported");
                }
            }
            else
            {
                throw new AppSystemException("PatientAuthorizeAttribute not supported");
            }
        }
    }
}

在 API 操作上,我们像这样使用它:

    [PatientAuthorize(PatientAccessRights.PATIENT_VIEW_APPOINTMENTS)] //this is enum, we can pass multiple
    [HttpPost]
    public SomeReturnType ViewAppointments()
    {
    }

这里的很多人已经说过这一点,但是使用策略处理程序,您可以在使用.NET Framework中的旧方法实现的目标方面走得很远。

我从这个答案中快速写了一篇关于SO:https://stackoverflow.com/a/61963465/7081176对我来说,在制作一些课程后,它可以完美运行:

编辑用户要求:

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,>((;

        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>

我希望这对面临此问题的任何人都有用。

我一直在研究解决一个非常相似的问题,并决定创建一个自定义的ActionFilterAttribute(我称之为AuthorizationFilterAttribute(而不是AuthorizeAttribute来实现这里的指导:https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-6.0#challenge-and-forbid-with-an-operational-resource-handler。