激动人心的问题
我试图通过尝试实现基于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身份表)
一般工作流程是:
policy
发出请求!并且在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);
}
惊人的问题:
激动人心的问题
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); }
它的工作方式就像我想要的方式。但是,再次,[[激动人心的问题到现在为止!
激动人心的问题