.net core 如何在.NETCore中使用自定义策略模式实现jwt令牌基身份验证?

.net core 如何在.NETCore中使用自定义策略模式实现jwt令牌基身份验证?,.net-core,jwt,authorization,asp.net-core-webapi,.net Core,Jwt,Authorization,Asp.net Core Webapi,我正试图通过实现基于jwt令牌的身份验证,在.NET Core 3.1中创建一个游乐场应用程序,并希望使用我的自定义策略模式(可怕?)。我可以很轻松地使用自定义的过滤器属性来实现这一点,这些属性是从.NET Framework中的AuthorizeAttribute派生而来的,但是使用.NET Core很困难。因为我使用了OnAuthorizationhook并捕获了HttpActionContext,解析令牌并检查角色策略等。。。但是现在我正在使用IAuthorizationHandler,到

我正试图通过实现基于jwt令牌的身份验证,在.NET Core 3.1中创建一个游乐场应用程序,并希望使用我的自定义策略模式(可怕?)。我可以很轻松地使用自定义的过滤器属性来实现这一点,这些属性是从.NET Framework中的
AuthorizeAttribute
派生而来的,但是使用.NET Core很困难。因为我使用了
OnAuthorization
hook并捕获了
HttpActionContext
,解析令牌并检查角色策略等。。。但是现在我正在使用
IAuthorizationHandler
,到目前为止,我还没有机会让它以我想要的方式工作。我读了很多例子和文章,但我找不到同样的方法来做我想做的事情

(注:当我搜索了几个小时,却找不到类似的方法时,这也让我感到非常紧张,因为我可能走错了路,或者试图重新发明方向盘……让我们看看我是否……)

我也在寻找答案(但很多人觉得这更容易管理——但对我来说,这似乎是一种过度的尝试。如果我错了,就怪我。)

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

在幕后,我在db中使用以及(我非常感激任何安全问题)

我的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标识表)

  • 每个用户都有角色(我有一个表作为角色,外部参照表作为用户角色)
  • 角色具有策略(我将表作为策略,将外部参照表作为 角色政策)
一般工作流程是:

  • 用户请求令牌(简单-登录时输入用户名和密码 第页)
  • 如果凭据有效,则向用户生成一个令牌,其中包括声明中的用户角色
  • 一旦用户向API方法发出请求,请检查 用户拥有的
    角色
    ,包括能够 提出那个要求
所以“基本上”,我希望获得令牌(随请求或索赔一起发送令牌),解决它,检查用户是否有我要寻找的内容(具体策略等),并基于此继续

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
,它是使用我的自定义策略属性
NeedsPolicyAttribute
从AuthorizationHandler派生的

需求政策属性:

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

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

[HttpGet][Route(“get/{id}”)]
[Authorize(“CanReadData”)]//这只允许我触发HandleRequirementAsync捕获的自定义属性,但我只能提供硬代码字符串
public ActionResultGetAppUser(int-id){
return\u 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);
}
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 ", "");
    }
}
[HttpGet]
[Route("get/{id}")]
[ExtendedAuthorize(PolicyEnum.CanReadData)]
public ActionResult < AppUserDto > GetAppUser(int id) {
    return _appUserManager.Get(id);
}
[HttpGet][Route(“get/{id}”)]
[MyPolicyAttrubute(MyPolicyEnum.Candobla)]//如果可能的话,我想在HandleRequirementAsync中捕获这一点,并与我的用户声明进行比较。。
public ActionResultGetAppUser(int-id){
return\u appUserManager.Get(id);
}
惊人的问题:

  • 我想重新发明轮子吗
  • 即使我是,这种方法是否有明显的不良做法
  • 还有其他/更好的建议吗

    • 在花了几个小时之后,我终于能够完成这项工作。所以我只是想把这篇文章作为我问题的答案,但我的“惊人的问题”(见上文问题末尾)仍然是。因此,请注意,此解决方案不能保证任何这些问题

      在我上面的问题中,
      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 > ();
        }
       }
      
      然后,我添加了用户拥有的策略:

      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;
      }
      
      TokenValidationHandler
      变成如下:

        [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);
      }
      
      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 ", "");
          }
      }
      
      [HttpGet]
      [Route("get/{id}")]
      [ExtendedAuthorize(PolicyEnum.CanReadData)]
      public ActionResult < AppUserDto > GetAppUser(int id) {
          return _appUserManager.Get(id);
      }
      
      公共类TokenValidationHandler:AuthorizationHandler{
      私有只读JwtSettings\u JwtSettings;
      专用只读IHttpContextAccessor\u contextAccessor;
      公共令牌验证处理程序(JwtSettings JwtSettings、IHttpContextAccessor contextAccessor){
      _jwtSettings=jwtSettings;
      _contextAccessor=contextAccessor;
      }
      受保护的覆盖任务HandleRequirementAsync(AuthorizationHandlerContext上下文,ExtendedAuthorizationAttribute要求){
      //注入IHttpContextAccessor以从请求中获取令牌。
      var rawToken=!_contextAccessor.HttpContext.Request.Headers.ContainsKey(“授权”)?string.Empty:_contextAccessor?.HttpContext?.Request?.Headers[“授权”].ToString();
      if(string.IsNullOrEmpty(rawToken)){
      context.Fail();
      返回Task.CompletedTask;
      }
      var-token=ScrubToken(rawToken);
      var handler=新的JwtSecurityTokenHandler();
      试一试{
      //验证给定的令牌,并在验证后为用户返回声明主体。
      var user=handler.ValidateToken(令牌,_jwtSettings.TokenValidationParame