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"
}

роль администратора работает!