如何在.net核心中使用自定义策略架构实现jwt令牌库身份验证以进行授权?

问题描述 投票:0回答:1

我试图通过尝试实现基于jwt令牌的身份验证在.NET Core 3.1中创建一个Playground应用,并希望使用我的自定义策略架构(吓人?)。我能够使用自定义的过滤器属性来做到这一点,该属性是从.NET Framework中的AuthorizeAttribute派生的,但是对.NET Core却很难。因为我正在使用OnAuthorization钩子并正在捕获HttpActionContext,解析令牌和检查角色策略等...但是现在我正在使用IAuthorizationHandler,但我没有机会使其按我的期望工作到目前为止。我阅读了许多示例和文章,但仍然找不到与我尝试的方法相同的方法。

(PS:当我搜索几个小时并且找不到类似的方法时,这也让我感到非常紧张,因为我可能会走完全错误的路线,或者试图重新发明方向盘。 。)

我也寻找了IdendityServer4(但是许多人发现它更容易管理-但对我来说我想做的事似乎太过分了。如果我错了,就怪我。)

到目前为止,我能够在用户登录时成功创建令牌。这是代码:((如果您想了解我的确切要求,请直接滚动到末尾,但是如果您要回答,请通读)

[在幕后,我在db中使用salted-hash password,在key-strecthing中使用PBKDF2 algorithm(我非常感谢任何安全方面的关注]

我的GenerateToken函数:

[HttpPost("token")]
public IActionResult GenerateToken(UserCredentialDto userCredentialDto) {
    bool isValidUser = _appUserManager.IsValidCredentials(userCredentialDto);

    if (!isValidUser) {
        return BadRequest("invalid user/pass combination");
    }

    // assume I am getting all the roles that user has and add them in claims.
    var claims = _appUserManager.GetUserClaims(userCredentialDto); 
    var key = new SymmetricSecurityKey(_jwtSettings.Key);
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    var token = new JwtSecurityToken(
    issuer: _jwtSettings.Issuer, audience: _jwtSettings.Audience, claims: claims, expires: DateTime.Now.AddMinutes(StaticAFUConfigHelper.TokenExpirationInMinutes), signingCredentials: creds);

    return Ok(new {
        token = new JwtSecurityTokenHandler().WriteToken(token)
    });
}

一般背景:(我没有使用aspnet身份表)

  • 每个用户都有角色(我有一个角色表,外部参照表有UserRole)
  • 角色有策略(我有一个表为Policy,外部引用表为RolePolicy)

一般工作流程是:

  • 用户请求令牌(简单-在登录时输入用户名和密码页面)
  • 如果有有效的凭据,则为用户生成一个令牌,其中包括声明中的用户角色。
  • 一旦用户请求API方法,请检查是否role,包括requiredpolicy发出请求!
  • 因此,“基本上”,我想获取令牌(将令牌与请求或声明一起发送),对其进行解析,检查用户是否拥有我想要的内容(特定政策等。),然后根据该内容进行操作。

并且在startup.cs中>

//Authentication services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options = >{ options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = "my issuer", ValidAudience = "my audience", IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String("assume this is my secret key")) }; options.SaveToken = true; }); // this part I'm also not sure as if I have 100s of policies, // would all of them has to be defined here? // and how I specifically assign this to an api method! Anyways please keep reading if you dont mind services.AddAuthorization(options => options.AddPolicy("CanReadData", policy => policy.Requirements.Add(new NeedsPolicyAttribute(PolicyEnum.CanReadData))));

然后我用我的自定义策略属性TokenValidationHandler创建了从AuthorizationHandler派生的NeedsPolicyAttribute。>

NeedsPolicyAttribute:

public class NeedsPolicyAttribute: IAuthorizationRequirement { public PolicyEnum RequiredPolicy { get; } public NeedsPolicyAttribute(PolicyEnum requiredPolicy) { RequiredPolicy = requiredPolicy; } }

并且HandleRequirementAsync是:

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NeedsPolicyAttribute requirement) { var myToken = "1234567889"; // just hardcoded for example - assume I got the JWT from the context. SecurityToken validatedToken; var handler = new JwtSecurityTokenHandler(); // assume there was no exception and I was able to validate the token which is a valid token... var user = handler.ValidateToken(myToken, _jwtSettings.TokenValidationParameters, out validatedToken); // ************************........ATTENTION HERE....... ***************** // I would like to check if the user has a role and which includes the policy which was required by the api method. //if so then, context.Succeed(requirement); //if not then context.Fail(); // and finally return Task.CompletedTask; }

而且我的示例API方法装饰为:

[HttpGet][Route("get/{id}")] [Authorize("CanReadData")] // THIS JUST LETS ME TRIGGER MY CUSTOM ATTRIBUTE TO BE CAPTURED BY HandleRequirementAsync BUT I CAN ONLY PROVIDE HARDCODE STRING public ActionResult < AppUserDto > GetAppUser(int id) { return _appUserManager.Get(id); }

我实际上想要做的是,用所需的策略装饰我的API方法,并验证给定的令牌具有具有包含所需的策略的角色的声明

类似以下内容:

[HttpGet][Route("get/{id}")] [MyPolicyAttrubute(MyPolicyEnum.CanDoBlaBla)] // I want to capture this in HandleRequirementAsync if possible and compare with my user claims.. public ActionResult < AppUserDto > GetAppUser(int id) { return _appUserManager.Get(id); }

惊人的问题:

    我要重新发明轮子吗?
  • 即使我是,这种方法是否有明显的不良做法?
  • 还有其他/更好的推荐吗?
  • 我正在尝试通过实现基于jwt令牌的身份验证在.NET Core 3.1中创建一个Playground应用,并希望使用我的自定义策略架构(吓人?)。我能够做到这一点...

花了几个小时,我得以完成这项工作。因此,我只是想将其发布为我的问题的答案,但是我的“

激动人心的问题

”(请参见上面问题的结尾)仍然存在。因此请注意,此解决方案不能保证所有这些问题。 我在以上问题中的StartUp.cs中的代码段中,我担心的一个问题是:

// if I have 100s of policies, would all of them have to be defined here? services.AddAuthorization(options => options.AddPolicy("CanReadData", policy => policy.Requirements.Add(new NeedsPolicyAttribute(PolicyEnum.CanReadData))));

因为示例代码是将它们作为硬编码的字符串一个接一个地添加,这从一开始就困扰我,因为我想使用Enum而不是硬编码的值。而且我不想在Startup.cs中添加很多行,每当我向应用程序添加新策略时也需要进行更新。

所以实际上很容易。我所做的只是:

我写了一个扩展程序以获取所有如下所示的枚举值:

public static class EnumUtils { public static IEnumerable < T > GetAllEnumValues < T > () { return System.Enum.GetValues(typeof(T)).Cast < T > (); } }

所以我可以像下面那样使用它。这样一来,我无需使用StartUp.cs即可将API方法之上的所有新创建的Policy枚举值用作属性。

services.AddAuthorization(options => { // add all the policies to option to be able to use in ExtendedAuthorizeAttribute on api methods. foreach(var policyEnum in EnumUtils.GetAllEnumValues < PolicyEnum > ()) options.AddPolicy(policyEnum.ToString(), policy => policy.Requirements.Add(new ExtendedAuthorizeAttribute(policyEnum))); });

然后我添加了用户拥有的策略:

public List < Claim > GetUserClaims(AuthRequestDto authRequestDto) { var userRoles = _unitOfWork.Roles.GetUserRoles(authRequestDto.UserId); var policies = userRoles.SelectMany(x = >x.RolePolicies.Where(p = >p.Policy.IsActive).Select(y = >y.Policy.Name)).Distinct().ToList(); var claims = new List < Claim > (); policies.ForEach(policy = >claims.Add(new Claim("UserPolicy", policy))); claims.Add(new Claim("Id", authRequestDto.UserId.ToString())); return claims; }

并将它们附加到我的令牌上,这样,一旦用户使用该令牌发出请求,我就可以解决它,并对照api方法的所需策略进行检查。

然后我创建了一个新的Attribute作为ExtendedAuthorizeAttribute,它是从AuthroizeAttribute派生并实现IAuthorizationRequirement

所以这里有两件事:我从AuthroizeAttribute派生了我的自定义属性,因为我希望它自动触发以进行授权以检查用户是否具有该api方法的必需策略。我实现了IAuthorizationRequirement,因为这使我可以在HandleRequirementAsync方法中将属性用作“要求”。

所以我创建的属性是:

/// <summary> /// Extended Authorize Attribute is derived from Authorize Attribute /// also implements IAuthorizationRequirement. /// Deriving from AuthorizeAttribute accepts only string for policy names /// By using this extension class, it let's me use Policy Enum then it converts it to string /// before passing it to AuthorizeAttribute which was not possible in controller. /// </summary> public class ExtendedAuthorizeAttribute: AuthorizeAttribute, IAuthorizationRequirement { public ExtendedAuthorizeAttribute(PolicyEnum policyEnum = PolicyEnum.General) : base(policyEnum.ToString()) {} }

[TokenValidationHandler变成如下:

public class TokenValidationHandler: AuthorizationHandler < ExtendedAuthorizeAttribute > { private readonly JwtSettings _jwtSettings; private readonly IHttpContextAccessor _contextAccessor; public TokenValidationHandler(JwtSettings jwtSettings, IHttpContextAccessor contextAccessor) { _jwtSettings = jwtSettings; _contextAccessor = contextAccessor; } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ExtendedAuthorizeAttribute requirement) { // injected the IHttpContextAccessor to get the token from the request. var rawToken = !_contextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization") ? string.Empty: _contextAccessor ? .HttpContext ? .Request ? .Headers["Authorization"].ToString(); if (string.IsNullOrEmpty(rawToken)) { context.Fail(); return Task.CompletedTask; } var token = ScrubToken(rawToken); var handler = new JwtSecurityTokenHandler(); try { // validates the given token and returns claims principal for user if validated. var user = handler.ValidateToken(token, _jwtSettings.TokenValidationParameters, out SecurityToken _); // Check if UserPolicies claims include the required the policy if (IsRequiredPolicyExistOnUser(user.Claims ? .ToList(), requirement)) { context.Succeed(requirement); } else { context.Fail(); } } catch(Exception e) { // TODO: Logging! context.Fail(); } return Task.CompletedTask; } private bool IsRequiredPolicyExistOnUser(List < Claim > userClaims, ExtendedAuthorizeAttribute requirement) { return userClaims != null && userClaims.Any() && userClaims.Where(x = >x.Type == "UserPolicy").Any(c = >c.Value == requirement.Policy.ToString()); } private string ScrubToken(string rawToken) { return rawToken.Replace("Bearer ", ""); } }

最后,我能够在如下所示的api方法上使用它:

[HttpGet][Route("get/{id}")][ExtendedAuthorize(PolicyEnum.CanReadData)] //[NonAction] public ActionResult < AppUserDto > GetAppUser(int id) { return _appUserManager.Get(id); }

它的工作方式就像我想要的方式。但是,再次,[[激动人心的问题到现在为止!
.net-core jwt authorization asp.net-core-webapi
1个回答
0
投票
花了几个小时,我得以完成这项工作。因此,我只是想将其发布为我的问题的答案,但是我的“

激动人心的问题

© www.soinside.com 2019 - 2024. All rights reserved.