Asp.Net Core鉴权授权:.Net Core对于JWT的封装

.NET封装了对于JWT的操作,让我们在程序中使用JWT进行鉴权和授权更简单。

ASP.NET Core中使用封装的JWT

第1步,我们先在配置系统appsettings.json中配置一个名字为JWT的节点,并在节点下创建SigningKey、ExpireSeconds两个配置项,分别代表JWT的密钥和过期时间(单位为秒)。
我们再创建一个对应JWT节点的配置类JWTOptions,类中包含SigningKey、ExpireSeconds这两个属性。

"JWT": {
  "SigningKey": "fasdfad&9045dafz222#fadpio@0232",
  "ExpireSeconds": "86400"
}
public class JWTOptions
{
    public string SigningKey { get; set; }
    public int ExpireSeconds { get; set; }
}

第2步,通过NuGet为项目安装Microsoft.AspNetCore.Authentication.JwtBearer包,这个包封装了简化ASP.NET Core中使用JWT的操作。

第3步,编写代码对JWT进行配置,把代码内容添加到Program.cs的builder.Build之前。

services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(x =>
    {
        var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOptions>();
        byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey);
        var secKey = new SymmetricSecurityKey(keyBytes);
        x.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = false,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = secKey
        };
    });

第4步,在Program.cs的app.UseAuthorization之前添加app.UseAuthentication

第5步,在TestController类中增加登录并且创建JWT的操作方法Login。

[HttpPost]
public async Task<IActionResult> Login(LoginRequest loginRequest, 
    [FromServices] IOptions<JWTOptions> jwtOptions)
{
    string userName = loginRequest.UserName;
    string password = loginRequest.Password;
    var user = await _userManager.FindByNameAsync(userName);
    var success = await _userManager.CheckPasswordAsync(user, password);
    if (!success)
    {
        return BadRequest("失败");
    }
    var claims = new List<Claim>();
    claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())) ;
    claims.Add(new Claim(ClaimTypes.Name, user.UserName));
    var roles = await _userManager.GetRolesAsync(user);
    foreach (var role in roles)
    {
        claims.Add(new Claim(ClaimTypes.Role, role));
    }
    string jwtToken = BuildToken(claims,jwtOptions.Value);
    return Ok(jwtToken);
}
private static string BuildToken(IEnumerable<Claim> claims, JWTOptions options)
{
    DateTime expires = DateTime.Now.AddSeconds(options.ExpireSeconds);
    byte[] keyBytes = Encoding.UTF8.GetBytes(options.SigningKey);
    var secKey = new SymmetricSecurityKey(keyBytes);
    var credentials = new SigningCredentials(secKey,
        SecurityAlgorithms.HmacSha256Signature);
    var tokenDescriptor = new JwtSecurityToken(expires: expires,
        signingCredentials: credentials, claims: claims);
    return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
}

第6步,在需要登录才能访问的控制器类上添加[Authorize]这个ASP.NET Core内置的Attribute。

[Route("[controller]/[action]")]
[ApiController]
[Authorize]
public class TempController : ControllerBase
{
    [HttpGet]
    public IActionResult Hello()
    {
        string id = this.User.FindFirst(ClaimTypes.NameIdentifier)!.Value;
        string userName = this.User.FindFirst(ClaimTypes.Name)!.Value;
        IEnumerable<Claim> roleClaims = this.User.FindAll(ClaimTypes.Role);
        string roleNames = string.Join(',', roleClaims.Select(c => c.Value));
        return Ok($"id={id},userName={userName},roleNames ={roleNames}");
    }
}

添加的[Authorize]表示这个控制器类下所有的操作方法都需要登录后才能访问。
ControllerBase中定义的ClaimsPrincipal类型的User属性代表当前登录用户的身份信息,我们可以通过ClaimsPrincipal的Claims属性获得当前登录用户的所有Claim信息,不过我们一般通过FindFirst方法根据Claim的类型来查找需要的Claim,如果用户身份信息中含有多个同类型的Claim,我们则可以通过FindAll方法来找到所有Claim。

完成代码后,运行项目,然后访问/Test/Login,并且在请求报文中填上正确的用户名和密码,请求成功并获得返回的JWT。
接下来我们访问/Temp/Hello路径。运行显示状态码401,没有授权,所以访问被拒绝。

ASP.NET Core要求(这也是HTTP的规范)JWT放到名字为Authorization的HTTP请求报文头中,报文头的值为“Bearer JWT”。默认情况下,我们无法在Swagger中添加请求报文头,因此我们需要借助第三方工具来发送带自定义报文头的HTTP请求。
以Postman为例,我们在请求的【Headers】中手工添加名字为Authorization的报文头,在Postman中发送的请求和服务器的响应如图所示。
image

Authorization的值中的“Bearer”和JWT之间一定要通过空格分隔。从服务器的响应可以看出,服务器返回的HTTP状态码为200,请求成功。

对于客户端获得的JWT,在前端项目中,我们可以把令牌保存到Cookie、LocalStorLocalStorage等位置,从而在后续请求中重复使用,而对于移动App、PC客户端,我们可以把令牌保存到配置文件中或者本地文件数据库中。当执行【退出登录】操作的时候,我们只要在客户端本地把JWT删除即可。

[Authorize]的注意事项

ASP.NET Core中身份验证和授权验证的功能由Authentication、Authorization中间件提供,我们在Program.cs中编写的app.UseAuthenticationapp.UseAuthorization就用于添加相应中间件到管道中。

[Authorize]这个Attribute既可以被添加到控制器类上,也可以被添加到操作方法上。我们可以在控制器类上标注[Authorize],那么这个控制器类中的所有操作方法都会被进行身份验证和授权验证;对于标注了[Authorize]的控制器类,如果其中某个操作方法不想被验证,我们可以在这个操作方法上添加[AllowAnonymous]。如果其中某个操作方法需要被验证,我们也可以不在类上添加,而在操作方法上添加[Authorize]。

在发送请求的时候,我们只要按照HTTP的要求,把JWT按照“Bearer JWT”格式放到名字为Authorization的请求报文头中即可。ASP.NET Core会按照HTTP的规范,从Authorization中取出令牌,并且进行校验、解析,然后把解析结果填充到User属性中,这一切都是ASP.NET Core完成的,不需要开发人员自己编写代码。

本文学习参考自:ASP.NET Core技术内幕与项目实战

posted @ 2022-09-27 11:39  一纸年华  阅读(646)  评论(0编辑  收藏  举报