在努力实施的同时基于角色的身份验证 using JWT作为默认身份验证方案,我遇到了一种情况,其中定义的角色Authorize
属性被忽略,允许任何请求(使用有效的令牌)通过,即使不在这些角色中,(有趣的是,具有相同定义的自定义要求的其他策略Authorize
属性工作正常)
Reading 杰里的文章他提到
这是一个很棒的发现:ASP.NET Core 中的 JWT 中间件知道如何解释 JWT 负载中的“角色”声明,并将适当的声明添加到ClaimsIdentity
。这使得使用[Authorize]
属性与角色非常容易。
And:
当您考虑将角色传递给[Authorize]
实际上会查看是否存在类型声明http://schemas.microsoft.com/ws/2008/06/identity/claims/role与您授权的角色的值。这意味着我可以简单地添加[Authorize(Roles = "Admin")]
任何 API 方法,这将确保只有有效负载包含声明“角色”且角色数组中包含 Admin 值的 JWT 才会被授权使用该 API 方法。
这仍然成立吗? (这篇文章已经好几年了)
我做错了什么吗?
启动(配置服务)
public void ConfigureServices(IServiceCollection services)
{
string defaultConnection = Configuration.GetConnectionString("Default");
services.AddDbContext<IdentityContext>(options => options.UseSqlServer(defaultConnection).UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll));
services.AddIdentity<AppUser, IdentityRole>()
.AddEntityFrameworkStores<IdentityContext>()
.AddDefaultTokenProviders();
services.AddAuthorization(o => o.AddPolicy(Policy.IsInTenant, x => x.AddRequirements(new IsInTenantRequirement())));
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = "somehost...",
ValidIssuer = "somehost...",
};
});
}
启动(配置)
public void Configure(IApplicationBuilder app, IWebHostEnvironment envy)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(x => x.MapControllers());
}
控制器:
[ApiController]
[Authorize(Roles = "some_random_string_which_is_not_registered_anywhere")] // <== any request with a valid token can access this controller
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
public string Get()
{
return "how are you?"
}
}
令牌服务
public class JwtService : ITokenService
{
private readonly JwtConfig _config;
public JwtService(IOptions<JwtConfig> config) => _config = config.Value;
public string GenerateRefreshToken(int size = 32)
{
var randomNumber = new byte[size];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
public string GenerateAccessToken(IEnumerable<Claim> claims)
{
var tokenHandler = new JwtSecurityTokenHandler();
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.Secret));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokeOptions = new JwtSecurityToken(
issuer: _config.Issuer,
audience: _config.Audience,
claims: claims,
expires: DateTime.Now.AddMinutes(int.Parse(_config.ExpirationInMinutes)),
signingCredentials: signinCredentials
);
return tokenHandler.WriteToken(tokeOptions);
}
public ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.Secret)),
ValidateLifetime = false
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
var jwtSecurityToken = securityToken as JwtSecurityToken;
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
return principal;
}
}
登录(使用令牌服务)
(目前我没有向令牌添加任何角色,尽管如此,用户可以完全访问由特定角色保护的资源。)
[AllowAnonymous]
[HttpPost()]
public async Task<IActionResult> Post(LoginDTO model)
{
if (!ModelState.IsValid) return BadRequest("errors.invalidParams");
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null)
{
return Unauthorized("errors.loginFailure");
}
var result = await _signInManager.PasswordSignInAsync(user?.UserName, model.Password, model.RememberMe, false);
if (result.Succeeded)
{
var claims = new List<Claim>
{
new Claim(AppClaim.TenantId, user.TenantId.ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
claims.AddRange(await _authOperations.GetUserRolesAsClaims(user));
claims.AddRange(await _authOperations.GetAllUserClaims(user));
var accessToken = _tokenService.GenerateAccessToken(claims);
var refreshToken = _tokenService.GenerateRefreshToken();
user.RefreshToken = refreshToken;
user.RefreshTokenExpiryTime = DateTime.Now.AddDays(7);
await _ctx.SaveChangesAsync();
return Ok(new TokenExchangeDTO
{
AccessToken = accessToken,
RefreshToken = refreshToken
});
}
应用程序设置.json
"JwtConfig": {
"Secret": "secret...",
"ExpirationInMinutes": 1440,
"Issuer": "somehost...",
"Audience": "somehost..."
}
如果需要额外的详细信息或更好的信息来回答我的问题,请告诉我。