ASP.NET Core JWT映射角色声明为ClaimsIdentity

我想使用JWT保护ASP.NET Core Web API。 另外,我想有一个选项,直接在控制器动作属性中使用来自令牌有效负载的角色。

现在,虽然我确实找到了如何将其与策略一起使用:

Authorize(Policy="CheckIfUserIsOfRoleX") ControllerAction()... 

我想最好选择使用通常的东西:

 Authorize(Role="RoleX") 

其中Role将从JWT有效负载自动映射。

 { name: "somename", roles: ["RoleX", "RoleY", "RoleZ"] } 

那么,在ASP.NET Core中实现这一目标的最简单方法是什么? 有没有办法通过一些设置/映射自动工作(如果是这样,在哪里设置它?)或者我应该在validation令牌后拦截ClaimsIdentity生成并手动添加角色声明(如果是,在何处/如何去做?)?

示例 – ASP.NET Core JWT

考虑这是有效载荷。

 { name:"somename", roles:["RoleX", "RoleY", "RoleZ"] } 

JWT MiddleWare

 public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { var keyAsBytes = Encoding.ASCII.GetBytes("mysuperdupersecret"); var options = new JwtBearerOptions { TokenValidationParameters = { IssuerSigningKey = new SymmetricSecurityKey(keyAsBytes) } }; app.UseJwtBearerAuthentication(options); app.UseMvc(); } } 

当我使用上面创建的JWT向我的API发出请求时,JWT中角色声明中的roles数组将自动添加为类型为http://schemas.microsoft.com/ws/2008/06/identity/claims/role对我的ClaimsIdentity。

您可以通过创建以下返回用户声明的简单API方法来测试此问题:

 public class ValuesController : Controller { [Authorize] [HttpGet("claims")] public object Claims() { return User.Claims.Select(c => new { Type = c.Type, Value = c.Value }); } } 

因此,当我调用上面的/claims端点并传递之前生成的JWT时,我将返回以下JSON:

 [ { "type": "name", "value": "someone" }, { "type": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "value": "RoleX" }, { "type": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "value": "RoleY" }, { "type": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "value": "RoleZ" } ] 

这真的很有趣的是当你考虑将角色传递到[Authorize]时,实际上会查看是否存在类型为http://schemas.microsoft.com/ws/2008/06/identity/claims/role的声明您授权的角色的价值。

这意味着我可以简单地将[Authorize(Roles = "Admin")]到任何API方法中,这将确保只有有效负载包含roles数组中包含Admin值的声明roles JWT才会被授权那个API方法。

 public class ValuesController : Controller { [Authorize(Roles = "Admin")] [HttpGet("ping/admin")] public string PingAdmin() { return "Pong"; } } 

现在只需使用[Authorize(Roles = "Admin")]装饰MVC控制器,只有ID令牌包含这​​些声明的用户才会被授权。

确保JWT的roles声明包含分配给用户的角色数组,并且可以在控制器中使用[Authorize(Roles = "???")] 。 这一切都无缝地工作。

生成JWT时,您需要获得有效的声明。 这是示例代码:

登录逻辑:

 [HttpPost] [AllowAnonymous] public async Task Login([FromBody] ApplicationUser applicationUser) { var result = await _signInManager.PasswordSignInAsync(applicationUser.UserName, applicationUser.Password, true, false); if(result.Succeeded) { var user = await _userManager.FindByNameAsync(applicationUser.UserName); // Get valid claims and pass them into JWT var claims = await GetValidClaims(user); // Create the JWT security token and encode it. var jwt = new JwtSecurityToken( issuer: _jwtOptions.Issuer, audience: _jwtOptions.Audience, claims: claims, notBefore: _jwtOptions.NotBefore, expires: _jwtOptions.Expiration, signingCredentials: _jwtOptions.SigningCredentials); //... } else { throw new ApiException('Wrong username or password', 403); } } 

获取基于UserRolesRoleClaimsUserClaims表的用户声明(ASP.NET标识):

 private async Task> GetValidClaims(ApplicationUser user) { IdentityOptions _options = new IdentityOptions(); var claims = new List { new Claim(JwtRegisteredClaimNames.Sub, user.UserName), new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()), new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64), new Claim(_options.ClaimsIdentity.UserIdClaimType, user.Id.ToString()), new Claim(_options.ClaimsIdentity.UserNameClaimType, user.UserName) }; var userClaims = await _userManager.GetClaimsAsync(user); var userRoles = await _userManager.GetRolesAsync(user); claims.AddRange(userClaims); foreach (var userRole in userRoles) { claims.Add(new Claim(ClaimTypes.Role, userRole)); var role = await _roleManager.FindByNameAsync(userRole); if(role != null) { var roleClaims = await _roleManager.GetClaimsAsync(role); foreach(Claim roleClaim in roleClaims) { claims.Add(roleClaim); } } } return claims; } 

Startup.cs请将所需的策略添加到授权中:

 void ConfigureServices(IServiceCollection service) { services.AddAuthorization(options => { // Here I stored necessary permissions/roles in a constant foreach (var prop in typeof(ClaimPermission).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)) { options.AddPolicy(prop.GetValue(null).ToString(), policy => policy.RequireClaim(ClaimType.Permission, prop.GetValue(null).ToString())); } }); } 

我是ASP.NET的初学者,所以如果你有更好的解决方案,请告诉我。

并且,我不知道将所有声明/权限放入JWT时有多糟糕。 太长? 表现? 我应该将生成的JWT存储在数据库中并稍后检查以获取有效用户的角色/声明吗?

为了生成JWT令牌,我们需要AuthJwtTokenOptions帮助类

 public static class AuthJwtTokenOptions { public const string Issuer = "SomeIssuesName"; public const string Audience = "https://awesome-website.com/"; private const string Key = "supersecret_secretkey!12345"; public static SecurityKey GetSecurityKey() => new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Key)); } 

帐户控制器代码:

 [HttpPost] public async Task GetToken([FromBody]Credentials credentials) { // TODO: Add here some input values validations User user = await _userRepository.GetUser(credentials.Email, credentials.Password); if (user == null) return BadRequest(); ClaimsIdentity identity = GetClaimsIdentity(user); return Ok(new AuthenticatedUserInfoJsonModel { UserId = user.Id, Email = user.Email, FullName = user.FullName, Token = GetJwtToken(identity) }); } private ClaimsIdentity GetClaimsIdentity(User user) { // Here we can save some values to token. // For example we are storing here user id and email Claim[] claims = new[] { new Claim(ClaimTypes.Name, user.Id.ToString()), new Claim(ClaimTypes.Email, user.Email) }; ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, "Token"); // Adding roles code // Roles property is string collection but you can modify Select code if it it's not claimsIdentity.AddClaims(user.Roles.Select(role => new Claim(ClaimTypes.Role, role))); return claimsIdentity; } private string GetJwtToken(ClaimsIdentity identity) { JwtSecurityToken jwtSecurityToken = new JwtSecurityToken( issuer: AuthJwtTokenOptions.Issuer, audience: AuthJwtTokenOptions.Audience, notBefore: DateTime.UtcNow, claims: identity.Claims, // our token will live 1 hour, but you can change you token lifetime here expires: DateTime.UtcNow.Add(TimeSpan.FromHours(1)), signingCredentials: new SigningCredentials(AuthJwtTokenOptions.GetSecurityKey(), SecurityAlgorithms.HmacSha256)); return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); } 

Startup.csservices.AddMvc调用之前,将以下代码添加到ConfigureServices(IServiceCollection services)方法:

 public void ConfigureServices(IServiceCollection services) { // Other code here… services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = AuthJwtTokenOptions.Issuer, ValidateAudience = true, ValidAudience = AuthJwtTokenOptions.Audience, ValidateLifetime = true, IssuerSigningKey = AuthJwtTokenOptions.GetSecurityKey(), ValidateIssuerSigningKey = true }; }); // Other code here… services.AddMvc(); } 

app.UseMvc调用之前, app.UseMvcStartup.cs ConfigureMethod添加app.UseAuthentication()调用。

 public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // Other code here… app.UseAuthentication(); app.UseMvc(); } 

现在您可以使用[Authorize(Roles = "Some_role")]属性。

要在任何控制器中获取用户ID和电子邮件,您应该这样做

 int userId = int.Parse(HttpContext.User.Claims.First(c => c.Type == ClaimTypes.Name).Value); string email = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.Email).Value; 

userId也可以这种方式重试(这是由于索赔类型名称ClaimTypes.Name

 int userId = int.Parse(HttpContext.User.Identity.Name); 

最好将此类代码移动到某些控制器扩展帮助程序:

 public static class ControllerExtensions { public static int GetUserId(this Controller controller) => int.Parse(controller.HttpContext.User.Claims.First(c => c.Type == ClaimTypes.Name).Value); public static string GetCurrentUserEmail(this Controller controller) => controller.HttpContext.User.Claims.First(c => c.Type == ClaimTypes.Email).Value; } 

您添加的任何其他Claim也是如此。 您应该只指定有效密钥。

这是我的工作代码! ASP.NET Core 2.0 + JWT。 将角色添加到JWT令牌。

appsettings.json

 "JwtIssuerOptions": { "JwtKey": "4gSd0AsIoPvyD3PsXYNrP2XnVpIYCLLL", "JwtIssuer": "http://yourdomain.com", "JwtExpireDays": 30 } 

Startup.cs

 // ===== Add Jwt Authentication ======== JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims // jwt // get options var jwtAppSettingOptions = Configuration.GetSection("JwtIssuerOptions"); services .AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(cfg => { cfg.RequireHttpsMetadata = false; cfg.SaveToken = true; cfg.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = jwtAppSettingOptions["JwtIssuer"], ValidAudience = jwtAppSettingOptions["JwtIssuer"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtAppSettingOptions["JwtKey"])), ClockSkew = TimeSpan.Zero // remove delay of token when expire }; }); 

AccountController.cs

 [HttpPost] [AllowAnonymous] [Produces("application/json")] public async Task GetToken([FromBody] LoginViewModel model) { var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, false, false); if (result.Succeeded) { var appUser = _userManager.Users.SingleOrDefault(r => r.Email == model.Email); return await GenerateJwtTokenAsync(model.Email, appUser); } throw new ApplicationException("INVALID_LOGIN_ATTEMPT"); } // create token private async Task GenerateJwtTokenAsync(string email, ApplicationUser user) { var claims = new List { new Claim(JwtRegisteredClaimNames.Sub, email), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(ClaimTypes.NameIdentifier, user.Id) }; var roles = await _userManager.GetRolesAsync(user); claims.AddRange(roles.Select(role => new Claim(ClaimsIdentity.DefaultRoleClaimType, role))); // get options var jwtAppSettingOptions = _configuration.GetSection("JwtIssuerOptions"); var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtAppSettingOptions["JwtKey"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var expires = DateTime.Now.AddDays(Convert.ToDouble(jwtAppSettingOptions["JwtExpireDays"])); var token = new JwtSecurityToken( jwtAppSettingOptions["JwtIssuer"], jwtAppSettingOptions["JwtIssuer"], claims, expires: expires, signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } 

Fiddler测试GetToken方法。 请求:

 POST https://localhost:44355/Account/GetToken HTTP/1.1 content-type: application/json Host: localhost:44355 Content-Length: 81 { "Email":"admin@admin.site.com", "Password":"ukj90ee", "RememberMe":"false" } 

调试响应令牌https://jwt.io/#debugger-io

有效载荷数据:

 { "sub": "admin@admin.site.com", "jti": "520bc1de-5265-4114-aec2-b85d8c152c51", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "8df2c15f-7142-4011-9504-e73b4681fb6a", "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin", "exp": 1529823778, "iss": "http://yourdomain.com", "aud": "http://yourdomain.com" } 

角色管理员工作!