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); } }
获取基于UserRoles
, RoleClaims
和UserClaims
表的用户声明(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.cs
在services.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.UseMvc
向Startup.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
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" }
角色管理员工作!
- 如何在请求结束时释放资源并在ASP.NET 5 / Core中处理注入的服务?
- 如何在IdentityServer4中添加自定义声明来访问令牌?
- ASP.NET Core Routing适用于VS IIS Express,但不适用于IIS 10
- .NET Core – 尝试将存储库添加到我的API控制器,但是当我执行每个控制器方法时都会返回500错误
- User.IsInRole在ASP.NET Core中没有返回任何内容(已实现存储库模式)
- 在生产中实例化ASP.NET Core应用程序
- 处理ASP.NET Core 1.0上的大型文件上传
- 为什么TestServer(AspNetCore)在静态文件上出现404错误?
- 如何在ASP.NET Core中将角色添加到Windows身份validation