• 8.2 JWT(代替Session)


    8.2 JWT(代替Session)

    经典的保持登陆状态的办法是Session,也就是用户登陆后,服务器产生唯一标识SessionId,并把SessionId和登陆的用户信息保存在服务器内存中,通时将SessionId发送给浏览器,当浏览器再次访问的时候,http请求中便携带了SessionId,服务器根据该Id在内存中取到用户信息,实现了登陆功能。

    Session的缺点:

    • 登陆用户多的时候,会占用服务器大量内存
    • 每次客户端请求都要向服务器获取一次Session,导致请求速度响应慢

    现在多采用JWT代替Session,JWT使用JSON来保存令牌信息,并不把信息保存在服务端,而是保存在客户端。JWT的结构包括头部、负载和签名:

    img

    头部中保存的是加密算法说明,负载中保存用户信息,签名是根据头部和负责经过算法算出来的值

    JWT的登陆流程:

    • 客户端向服务单发送登陆请求
    • 服务端检验用户名密码,如果成功将从数据库中提取出这个用户的Id、角色等信息
    • 服务端采用自定义的秘钥对用户信息(JSON)进行签名,形成签名数据
    • 将用户信息(JSON)和上一步形成的签名拼接形成JWT,发送给客户端
    • 客户端每次请求都带上这个JWT
    • 每次服务器收到带JWT的请求后,使用自定义的秘钥对JWT的签名进行校验,如果成功则从JWT中取出用户信息

    JWT基本使用

    1. NuGet安装System.IdentityModel.Tokens.Jwt

    2. 生成JWT的程序

    using Microsoft.IdentityModel.Tokens;
    using System.IdentityModel.Tokens.Jwt;
    using System.Security.Claims;
    using System.Text;
    //每个claim代表一条信息,一个用户可能有多条信息如Id,name,所以这里用列表
    var claims = new List<Claim>();
    //Claim具有两个属性Type和Value,他们都是string类型 Type:用户信息类型 Value:用户信息的值
    claims.Add(new Claim(ClaimTypes.NameIdentifier, "6"));
    claims.Add(new Claim(ClaimTypes.Name, "yzk"));
    claims.Add(new Claim(ClaimTypes.Role, "User"));
    claims.Add(new Claim(ClaimTypes.Role, "Admin"));//一个Type下允许有多个value
    claims.Add(new Claim("PassPort", "E90000082"));
    //签名秘钥,自定义,长一些安全
    string key = "fasdfad&9045dafz222#fadpio@0232";
    DateTime expires = DateTime.Now.AddDays(1);//设置令牌过期时间
    {//根据过期时间、多个claim、秘钥生成JWT
    	byte[] secBytes = Encoding.UTF8.GetBytes(key);
    	var secKey = new SymmetricSecurityKey(secBytes);
    	var credentials = new SigningCredentials(secKey,SecurityAlgorithms.HmacSha256Signature);
    	var tokenDescriptor = new JwtSecurityToken(claims: claims,
        expires: expires, signingCredentials: credentials);
    	string jwt = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
    }
    Console.WriteLine(jwt);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    JWT其实是明文,所以不要将重要的信息放到JWT中,为了防止篡改JWT,服务器需要对JWT签名进行验证

    1. JWT验证
    string jwt = Console.ReadLine()!;
    string secKey = "fasdfad&9045dafz222#fadpio@0232";//与之前的保持一致
    JwtSecurityTokenHandler tokenHandler = new();
    TokenValidationParameters valParam = new();
    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secKey));
    valParam.IssuerSigningKey = securityKey;
    valParam.ValidateIssuer = false;
    valParam.ValidateAudience = false;
    //解密
    ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwt,
    		valParam, out SecurityToken secToken);
    foreach (var claim in claimsPrincipal.Claims)
    {
    	Console.WriteLine($"{claim.Type}={claim.Value}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ASP.NET Core对JWT的封装

    1. 在配置系统中增加JWT节点,节点下设置秘钥和过期时间,再创建一个对应的配置类
    "JWT": {
      "SigningKey": "fasdfad&9045dafz222#fadpio@0232",
      "ExpireSeconds": "86400"
    }
    
    • 1
    • 2
    • 3
    • 4
    public class JWTOptions
    {
        public string SigningKey { get; set; }
        public int ExpireSeconds { get; set; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. NuGet安装Microsoft.AspNetCore.Authentication.JwtBearer

    2. 对JWT进行配置在builder.Build之前添加

    services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));//实体配置类
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)//配置授权的各种属性
    .AddJwtBearer(x => //配置JWT的承载
    {
        //配置JWT绑定到JWTOptions新的实例,返回一个JWTOptions实例
    	JWTOptions? jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOptions>();
    	byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey);
    	var secKey = new SymmetricSecurityKey(keyBytes);
    	x.TokenValidationParameters = new()//设置令牌验证参数
    	{
    		ValidateIssuer = false,
    		ValidateAudience = false,
    		ValidateLifetime = true,
    		ValidateIssuerSigningKey = true,
    		IssuerSigningKey = secKey
    	};
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 在Program.cs中的app.UseAuthorization()前面加上app.UseAuthentication();

    2. 在控制类中增加登陆并且创建JWT的操作方法

    public record LoginRequest (string UserName,string Password)
    
    [HttpPost]
    public async Task<IActionResult> Login2(LoginRequest req,
    			[FromServices] IOptions<JWTOptions> jwtOptions)
    {
    	string userName = req.UserName;
    	string password = req.Password;
    	var user = await userManager.FindByNameAsync(userName);
    	if (user == null)
    	{
    		return NotFound($"用户名不存在{userName}");
    	}
    	var success = await userManager.CheckPasswordAsync(user, password);
    	if (!success)
    	{
    		return BadRequest("Failed");
    	}
        //登陆成功
    	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 (string role in roles)
    	{
    		claims.Add(new Claim(ClaimTypes.Role, role));
    	}
    	string jwtToken = BuildToken(claims, jwtOptions.Value);//登陆成功后创建JWT
    	return Ok(jwtToken);//将jwt返回给前端
    }
    
    //创建JWT
    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 "Bearer "+new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    1. 在需要登陆才能访问的控制器类上添加[Authorize]
    [Route("[controller]/[action]")]
    [ApiController]
    [Authorize]
    public class Test2Controller : ControllerBase
    {
    	[HttpGet]
    	public IActionResult Hello()
    	{
            //ControllerBase类中定义的User属性中含有当前登陆用户的所有信息
    		string id = this.User.FindFirst(ClaimTypes.NameIdentifier)!.Value;//!不为空
    		string userName = this.User.FindFirst(ClaimTypes.Name)!.Value;
    		return Ok($"id={id},userName={userName}");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在请求的时候,ASP.Net Core要求,JWT要放到请求报文头中,其key为Authorization ,值为Bearer JWT值。

    对于客户端获得的JWT,在前端项目中可以将其保存在Cookie或者LocalStorage等位置,在退出的时候进行删除。

    [Authorize]注意事项

    [Authorize]可以加到控制器上也可以加到操作方法中,在控制器上标注表示该控制器下所有的操作方法都需要身份验证,如果某个操作方法不需要身份验证则在该操作方法上标注[AllowAnonymous]

    在Swagger带有Authorize按钮

    修改Program.cs

    builder.Services.AddSwaggerGen(c =>
    {
        var scheme = new OpenApiSecurityScheme()
        {
            Description = "Authorization header. \r\nExample: 'Bearer 12345abcdef'", //描述
            Reference = new OpenApiReference{Type = ReferenceType.SecurityScheme,
                Id = "Authorization"},
            Scheme = "oauth2",Name = "Authorization",
            In = ParameterLocation.Header,Type = SecuritySchemeType.ApiKey,
        };
        c.AddSecurityDefinition("Authorization", scheme);
        var requirement = new OpenApiSecurityRequirement();
        requirement[scheme] = new List<string>();
        c.AddSecurityRequirement(requirement);
    }); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    界面如图:
    在这里插入图片描述
    在这里插入图片描述

    在对话框中输入Bearer JWT

  • 相关阅读:
    国家开放大学 模拟 试题 训练
    java面试题:java中的单例设计模式及两种实现方法的代码举例
    [c++ STL]set使用详解
    Java 基础:设计模式之工厂方法模式
    CentOS 安装 rabbitMQ
    为什么要学习TypeScript
    【距离注意残差网络:超分】
    Databend 开源周报 #66
    解决Navicat中文注释乱码问题
    Java注解(3):一个真实的Elasticsearch案例
  • 原文地址:https://blog.csdn.net/weixin_44064908/article/details/126562866