簡體   English   中英

如何在 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 授權中收到 session ID。 從那個 ID 我會知道一個特定的動作是否有效。

ASP.Net Core 團隊推薦的方法是使用此處完整記錄的新策略設計。 新方法背后的基本思想是使用新的[Authorize]屬性來指定“策略”(例如[Authorize( Policy = "YouNeedToBe18ToDoThis")] ,其中策略在應用程序的Startup.cs中注冊以執行某些塊代碼(即確保用戶有年齡聲明,其中年齡為 18 歲或以上)。

策略設計是對框架的一個很好的補充,應該贊揚 ASP.Net 安全核心團隊的引入。 也就是說,它並不適合所有情況。 這種方法的缺點是它無法為簡單地斷言給定控制器或動作需要給定聲明類型的最常見需求提供方便的解決方案。 在應用程序可能擁有數百個離散權限來控制對單個 REST 資源(“CanCreateOrder”、“CanReadOrder”、“CanUpdateOrder”、“CanDeleteOrder”等)的 CRUD 操作的情況下,新方法要么需要重復的一對一策略名稱和聲明名稱之間的一個映射(例如options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder)); ),或編寫一些代碼以在運行時執行這些注冊(例如,從數據庫中讀取所有聲明類型並在循環中執行上述調用。在大多數情況下,這種方法的問題在於它是不必要的開銷。

雖然 ASP.Net Core 安全團隊建議永遠不要創建自己的解決方案,但在某些情況下,這可能是開始時最謹慎的選擇。

下面是一個實現,它使用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;
        }
    }
}

基於 Derek Greer GREAT answer,我用枚舉做到了。

這是我的代碼示例:

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 來查找控制器和操作上的自定義屬性,並將它們傳遞給 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。 它將在 HandleRequirementsAsync 方法中為您的所有自定義屬性傳遞一個 IEnumerable,這些屬性是從您的 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 添加到服務中,並添加您的 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
    }
}

當前制作自定義 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() { ... }
}

對於身份驗證,最好在中間件級別處理。

你到底想達到什么目的?

什么?!

我決定添加另一個簡單的答案。 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";
}

現代方式是 AuthenticationHandlers

在 startup.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";
    }
}

如果有人只想使用當前的安全實踐在授權階段驗證不記名令牌,您可以,

將此添加到您的 Startup/ConfigureServices

    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)]

接受的答案( 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.
     */
            }
        }); 

DefaultAuthorizationRequirement 類看起來像......

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

請注意,上面的代碼還可以將用戶預映射到數據存儲中的策略。 因此,在為用戶撰寫聲明時,您基本上會直接或間接檢索已預先映射到用戶的策略(例如,因為用戶具有特定聲明值並且該聲明值已被識別並映射到策略,例如它為也具有該聲明值的用戶提供自動映射),並將策略作為聲明登記,這樣在授權處理程序中,您可以簡單地檢查用戶的聲明是否包含 require.Policy 作為其聲明項的值索賠。 那是為了滿足策略要求的靜態方式,例如“名字”要求本質上是相當靜態的。 因此,對於上面的示例(我在之前對此答案的更新中忘記提供有關 Authorize 屬性的示例),使用具有 Authorize 屬性的策略如下所示,其中 ViewRecord 是枚舉成員:

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

動態需求可以是關於檢查年齡范圍等,並且使用這些需求的策略不能預先映射到用戶。

@blowdart ( https://stackoverflow.com/a/31465227/4974715 ) 給出的答案已經是動態策略聲明檢查的示例(例如,檢查用戶是否超過 18 歲)。

PS:我在手機上輸入的。 請原諒任何拼寫錯誤和格式缺失。

下面的代碼在.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;
    }
}

在撰寫本文時,我相信這可以通過 asp.net core 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 的 Authorization 標頭。 這是原型,我相信下周我們會在我們的生產系統中做一些非常接近這個的事情。

未來的選民,請在投票時考慮寫作日期。 截至今天,這works on my machine. ™ 您可能需要更多的錯誤處理和日志記錄您的實現。

只是添加來自@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 的方式已經改變。

我有不記名令牌,我可以閱讀索賠。 我在控制器和動作上使用該屬性

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

這里很多人已經告訴過這一點,但是使用策略處理程序,您可以在 .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;
    }
}

注冊我的處理程序和要求: services.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>

我希望這對任何面臨這個問題的人有用。

我一直在研究解決一個非常相似的問題,並決定創建一個自定義 ActionFilterAttribute(我將稱之為 AuthorizationFilterAttribute)而不是 AuthorizeAttribute 來實施此處的指導: https ://docs.microsoft.com/en- 我們/aspnet/core/security/authorization/resourcebased?view=aspnetcore-6.0#challenge-and-forbid-with-an-operational-resource-handler

這是一個簡單的 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"));
        }
    }
}

在 Program.cs 中添加處理程序:

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

在 program.cs 中添加具有您的角色要求的策略:

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

使用您的政策:

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

在我們的應用程序中進行授權。 我們必須根據授權屬性中傳遞的參數調用服務。

例如,如果我們想檢查登錄的醫生是否可以查看患者預約,我們會將“View_Appointment”傳遞給自定義授權屬性,並在數據庫服務中檢查該權限,並根據結果進行授權。 這是此場景的代碼:

    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()
    {

    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM