.net core 如何在.NETCore中使用自定义策略模式实现jwt令牌基身份验证?
我正试图通过实现基于jwt令牌的身份验证,在.NET Core 3.1中创建一个游乐场应用程序,并希望使用我的自定义策略模式(可怕?)。我可以很轻松地使用自定义的过滤器属性来实现这一点,这些属性是从.NET Framework中的.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,到
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