[英]Can I use the Authorize attribute filtering by Roles and Claims when using ASP.NET Identity and JWT authentication from external providers?
[英]JWT Authentication, roles defined in Authorize attribute are ignored
在嘗試使用JWT作為默認身份驗證方案來實現基於角色的身份驗證時,我遇到了這樣一種情況,即在Authorize
屬性中定義的角色被忽略,允許任何請求(帶有有效令牌)通過,即使不在那些角色,(有趣的是,在同一Authorize
屬性中定義的具有自定義要求的其他策略運行良好)
閱讀傑里的文章,他提到
這是一個很好的發現:ASP.NET Core 中的 JWT 中間件知道如何解釋 JWT 有效負載中的“角色”聲明,並將適當的聲明添加到
ClaimsIdentity
。 這使得使用 Roles 的[Authorize]
屬性非常容易。
和:
真正有趣的是,當您考慮將角色傳遞給
[Authorize]
時,實際上會查看是否存在類型為http://schemas.microsoft.com/ws/2008/06/identity/claims/role的聲明您授權的角色的價值。 這意味着我可以簡單地將[Authorize(Roles = "Admin")]
添加到任何 API 方法中,這將確保只有負載包含聲明“角色”的 JWT 才會在角色數組中包含 Admin 的值授權該 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());
}
Controller:
[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..."
}
如果需要更多詳細信息或更好的信息來回答我的問題,請告訴我。
這是關於如何使用 JWT 基於角色的身份驗證的完整工作演示:
啟動.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();
}
}
結果:
參考:
事實證明,我錯誤地將我的自定義授權處理程序之一配置為接受基本IAuthorizationRequirement
作為 Requirement 參數類型,而不是特定的派生 Requirement,因此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;
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.