JWT Authentication,Authorize 属性中定义的角色被忽略
Posted
技术标签:
【中文标题】JWT Authentication,Authorize 属性中定义的角色被忽略【英文标题】:JWT Authentication, roles defined in Authorize attribute are ignored 【发布时间】:2021-03-21 10:53:00 【问题描述】:在尝试使用 JWT 作为默认身份验证方案来实现 role-based-authentication 时,我遇到了Authorize
属性中定义的角色正在被忽略,允许任何请求(具有有效令牌)通过,即使不是在这些角色中,(有趣的是,在相同 Authorize
属性中定义的具有自定义要求的其他策略工作正常)
阅读jerrie's artical他提到了这一点
这是一个很好的发现:ASP.NET Core 中的 JWT 中间件知道如何解释 JWT 有效负载中的“角色”声明,并将适当的声明添加到
ClaimsIdentity
。这使得在角色中使用[Authorize]
属性非常容易。
还有:
真正有趣的地方在于,当您考虑将角色传递给
[Authorize]
时,实际上会查看是否存在具有您授权的角色值的http://schemas.microsoft.com/ws/2008/06/identity/claims/role 类型的声明。这意味着我可以简单地将[Authorize(Roles = "Admin")]
添加到任何 API 方法中,这将确保只有有效负载包含声明“角色”的 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
);
appsettings.json
"JwtConfig":
"Secret": "secret...",
"ExpirationInMinutes": 1440,
"Issuer": "somehost...",
"Audience": "somehost..."
如果需要更多详细信息或更好的信息来回答我的问题,请告诉我。
【问题讨论】:
你是如何生成令牌的? 【参考方案1】:事实证明,我错误地将我的一个自定义授权处理程序配置为接受基本 IAuthorizationRequirement
作为需求参数类型,而不是特定的派生需求,因此基本上任何需求都调用了 context.Succeed(requirement)
将其标记为成功。
原代码:
public class IsInTenantRequirement : IAuthorizationRequirement
public class IsInTenantAuthorizationHandler : AuthorizationHandler<IAuthorizationRequirement>
private readonly RouteData _routeData;
public IsInTenantAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
_routeData = httpContextAccessor.HttpContext.GetRouteData();
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IAuthorizationRequirement requirement)
var tenantIdFromRequest = _routeData.Values["tenantId"]?.ToString();
var tenantId = context.User.FindFirstValue(AppClaim.TenantId);
if (tenantIdFromRequest == tenantId)
context.Succeed(requirement);
return Task.CompletedTask;
更新后的代码:
public class IsInTenantRequirement : IAuthorizationRequirement
public class IsInTenantAuthorizationHandler : AuthorizationHandler<IsInTenantRequirement>
private readonly RouteData _routeData;
public IsInTenantAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
_routeData = httpContextAccessor.HttpContext.GetRouteData();
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsInTenantRequirement requirement)
var tenantIdFromRequest = _routeData.Values["tenantId"]?.ToString();
var tenantId = context.User.FindFirstValue(AppClaim.TenantId);
if (tenantIdFromRequest == tenantId)
context.Succeed(requirement);
context.Succeed(requirement);
return Task.CompletedTask;
【讨论】:
【参考方案2】:以下是有关如何使用基于 JWT 角色的身份验证的完整演示:
Startup.cs
public void ConfigureServices(IServiceCollection services)
services.AddControllers();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
options.TokenValidationParameters = new TokenValidationParameters()
ValidIssuer = Configuration["Jwt:JwtIssuer"],
ValidAudience = Configuration["Jwt:JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:JwtKey"])),
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
;
);
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
endpoints.MapControllerRoute(
name: "default",
pattern: "controller=Home/action=Index/id?");
endpoints.MapControllers();
);
在 appSettings.json 中存储 Issuer
、Audience
和 SigningKey
:
"jwt":
"JwtKey": "YourJwtKey",
"JwtIssuer": "YourJwtIssuer"
生成令牌:
[Route("api/[Controller]")]
[ApiController]
public class ValuesController : ControllerBase
private IConfiguration _config;
public ValuesController(IConfiguration config)
_config = config;
[Route("GenerateToken")]
public async Task<IActionResult> GenerateToken()
var claims = new List<Claim>
new Claim(ClaimTypes.Role, "Admin")
;
var token = new JwtSecurityToken(_config["Jwt:JwtIssuer"],
_config["Jwt:JwtIssuer"],
claims: claims,
expires: DateTime.Now.AddDays(5),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:JwtKey"])),
SecurityAlgorithms.HmacSha256));
var data = new JwtSecurityTokenHandler().WriteToken(token);
return Ok(new data );
测试方法:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
[Authorize(Roles = "admin")]
[HttpGet]
public async Task<IActionResult> Get()
return Ok();
[Authorize(Roles = "Admin")]
[HttpGet("GetAdmin")]
public async Task<IActionResult> GetAdmin()
return Ok();
结果:
参考:
https://***.com/a/61403262/11398810
【讨论】:
谢谢你的回答,看来我做的一切都是正确的,我已经添加了你需要的部分 查看您的示例项目1drv.ms/u/s!AkZwXaPsV4CxgkbR_a95JsHjbb6s?e=zUWTHS 似乎您正在使用需要角色的策略,而不是使用内置角色功能 嗨@RafiHenig,不,你不需要检查我以前的项目。我现在所做的使用内置角色功能。实际上,我很高兴你已经解决了你的问题。跨度>以上是关于JWT Authentication,Authorize 属性中定义的角色被忽略的主要内容,如果未能解决你的问题,请参考以下文章
wp rest api 授权方法步骤(使用JWT Authentication插件)
JWT Authentication,Authorize 属性中定义的角色被忽略
JWT spring security Authentication过滤器丢失标头信息
Spring Cloud authentication with JWT service