C# ASP.NET核心JWT映射角色声明索赔实体

C# ASP.NET核心JWT映射角色声明索赔实体,c#,asp.net-core,asp.net-core-webapi,C#,Asp.net Core,Asp.net Core Webapi,我想使用JWT保护ASP.NET核心Web API。此外,我希望可以选择直接在控制器操作属性中使用令牌有效负载中的角色 现在,虽然我确实了解了如何将其与策略结合使用: Authorize(Policy="CheckIfUserIsOfRoleX") ControllerAction()... 我希望有一个更好的选择来使用一些常见的东西,如: Authorize(Role="RoleX") 其中角色将从JWT有效负载自动映射 { name: "somename", roles:

我想使用JWT保护ASP.NET核心Web API。此外,我希望可以选择直接在控制器操作属性中使用令牌有效负载中的角色

现在,虽然我确实了解了如何将其与策略结合使用:

Authorize(Policy="CheckIfUserIsOfRoleX")
ControllerAction()...
我希望有一个更好的选择来使用一些常见的东西,如:

Authorize(Role="RoleX")
其中角色将从JWT有效负载自动映射

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

那么,在ASP.NET内核中实现这一点最简单的方法是什么?是否有办法通过一些设置/映射(如果有,在何处设置?)使其自动工作?或者,在验证令牌后,我是否应该拦截
ClaimsIdentity
的生成并手动添加角色声明(如果有,在何处/如何执行?

生成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);
    }
}
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()));
        }
    });
}
不允许:

public static class ClaimPermission
{
    public const string
        CanAddNewService = "Tự thêm dịch vụ",
        CanCancelCustomerServices = "Hủy dịch vụ khách gọi",
        CanPrintReceiptAgain = "In lại hóa đơn",
        CanImportGoods = "Quản lý tồn kho",
        CanManageComputers = "Quản lý máy tính",
        CanManageCoffees = "Quản lý bàn cà phê",
        CanManageBillards = "Quản lý bàn billard";
}
使用类似的代码段获取所有预定义的权限,并将其插入asp.net权限声明表:

var staffRole = await roleManager.CreateRoleIfNotExists(UserType.Staff);

foreach (var prop in typeof(ClaimPermission).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy))
{
    await roleManager.AddClaimIfNotExists(staffRole, prop.GetValue(null).ToString());
}
我是ASP.NET的初学者,如果您有更好的解决方案,请告诉我


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

要生成JWT令牌,我们需要
AuthJwtTokenOptions
helper类

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);
}
app.UseMvc
调用之前,还添加
app.UseMvc的
ConfigureMethod
调用
Startup.cs

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
(这是由于索赔类型name
ClaimTypes.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;
}

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

这是我的工作代码!ASP.NET核心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<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);
}
调试响应令牌

有效载荷数据:

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

角色管理员已工作

这是一个完美的答案!大多数其他解决方案都不会获得角色索赔。对象
claunsipmission
来自何处?对于正在查看
claunsipmission
的任何人,请查看已编辑的答案。您可以使用单行
索赔替换loop
foreach(角色中的var角色).
。添加范围(角色。选择(角色=>新索赔(ClaimsIdentity.DefaultRoleClaimType,role));
感谢瓦迪姆更正了对周期的描述和建议。我做了一个更改!为什么不使用新声明(JwtRegisteredClaimNames.Sub,email)而不是新声明(JwtRegisteredClaimNames.Sub,user.email),正确。您只能传递ApplicationUser参数。然后将其全部用于标记。重构将改进我的代码。
// ===== 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
        };
    });
[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);
}
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"
}
{
  "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"
}