ASP.NET основные утверждения роли сопоставления JWT к ClaimsIdentity
Я хочу защитить ASP.NET Core Web API с использованием JWT. Кроме того, я хотел бы иметь возможность использовать роли из полезной нагрузки токенов непосредственно в атрибутах действий контроллера.
теперь, когда я узнал, как использовать его с политиками:
Authorize(Policy="CheckIfUserIsOfRoleX")
ControllerAction()...
Я хотел бы лучше иметь возможность использовать что-то обычное, типа:
Authorize(Role="RoleX")
где роль будет автоматически отображаться из полезной нагрузки JWT.
{
name: "somename",
roles: ["RoleX", "RoleY", "RoleZ"]
}
Итак, каков самый простой способ выполните это в ASP.NET ядро? Есть ли способ заставить это работать автоматически через некоторые настройки / сопоставления (если да, то где его установить?) или я должен, после проверки токена, перехватить генерацию ClaimsIdentity
и добавить претензии ролей вручную (если да, то где/как это сделать?)?
4 ответов
образец - ASP.NET Core JWT
считайте, что это полезная нагрузка.
{
name:"somename",
roles:["RoleX", "RoleY", "RoleZ"]
}
промежуточное ПО JWT
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();
}
}
когда я делаю запрос к моему API с JWT, созданным выше, массив ролей в roles
утверждение в JWT будет автоматически добавлено как утверждения с типом 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, и это гарантирует, что только JWTs, где полезная нагрузка содержит утверждение roles
содержащее значение Admin в массиве ролей будет авторизовано для этого метода API.
public class ValuesController : Controller
{
[Authorize(Roles = "Admin")]
[HttpGet("ping/admin")]
public string PingAdmin()
{
return "Pong";
}
}
теперь просто украсьте контроллеры MVC с [Authorize(Roles = "Admin")]
и только пользователи, чей маркер ID содержит эти утверждения, будут авторизованы.
обеспечить roles
утверждение вашего JWT содержит массив ролей, назначенных пользователю, и вы можете использовать [Authorize(Roles = "???")]
в своих контроллерах. Все работает без проблем.
вам нужно получить действительные утверждения при создании JWT. Вот пример кода:
логика входа:
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> 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 Identity):
private async Task<List<Claim>> GetValidClaims(ApplicationUser user)
{
IdentityOptions _options = new IdentityOptions();
var claims = new List<Claim>
{
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<IActionResult> 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
добавить следующий код ConfigureServices(IServiceCollection services)
перед метод services.AddMvc
звоните:
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.UseAuthentication()
вызов ConfigureMethod
of Startup.cs
до app.UseMvc
звонок.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Other code here…
app.UseAuthentication();
app.UseMvc();
}
теперь вы можете использовать [Authorize(Roles = "Some_role")]
атрибуты.
чтобы получить идентификатор пользователя и электронную почту в любом контроллере, вы должны сделать это так это
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
}
Автозагрузка.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<object> 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<object> GenerateJwtTokenAsync(string email, ApplicationUser user)
{
var claims = new List<Claim>
{
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);
}
тест скрипач 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"
}
роль администратора работает!