JWT?

什么是JWT?

json web token
是一种验证授权的东西。后端生成一个token令牌,返回给前端,前端自行保存。下次请求的时候带上这个token令牌,才有权限,不然没有权限。

token令牌什么样子?

token令牌用.分成三份
header.payload.signature
第一段是header的base64编码,第二段是payload的base64编码。第三段是加密算出来的签名

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJyZWZyZXNoIiwibmJmIjoxNjI2MDg1NTA4LCJleHAiOjE2MjYwODYxMDgsImlzcyI6IuW8oOS4iSIsImF1ZCI6IuadjuWbmyJ9.UpnhTDhjwUX4tZrrvpviXFKDdzfXOfVPkD598lAeH0o

这个网站可以查看jwt.io

header是啥?

一个json格式,里面包含了,使用的加密算法等信息

  1. {
  2. "alg": "HS256",
  3. "typ": "JWT"
  4. }

payload是啥?

也是一个json格式,里面包含了一些自带定义的东西(创建token事可以选择不加),过期时间,颁发令牌的是谁,验证令牌的是谁,还可以自定义加键值对。

  1. {
  2. "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "refresh",
  3. "nbf": 1626085508,
  4. "exp": 1626086108,
  5. "iss": "张三",
  6. "aud": "李四"
  7. }

signature是啥?

一个签名,防篡改token的。
前面的header和payload,都会编码成base64字符串。服务的还有一个key,就是密钥。signature签名就是header的base64字符串 加上 payload的base64字符串,再用密钥key进行某种算法的加密而生成的。

怎么个防篡改法?

header和payload里面的东西是公开的,base64防小人不妨君子啊。最好不要在payload里面放敏感信息,如密码等。因为有心人可以查看。
如果实在要放点什么信息,加密后在放。

整个token防篡改的最关键地方就在于最后的签名。
签名通过header和payload和key一起算出来的。

服务端一般在登录的时候给客户端发送一个token,之后的客户端请求服务的,都要带上这个token在请求头里面。服务端接到了这个请求,根据请求头里面携带的token的header和payload加上服务端设置的key密钥进行签名计算。算出来的签名如果和发过来的签名不一样,就证明header或者payload被修改了。验算不通过。

只要密钥key在服务端不被盗取,就是安全的。哪怕header和payload被修改了一点点,签名算出来都截然不同。

但是token被被人拿到了,在有效期内他都可以通过这个token进行访问。jwt是无状态的。不认用户,只认token。

安装包

Microsoft.AspNetCore.Authentication.JwtBearer

生成token

  1. public string GenerateAccessToken(JWTData_json jwtdata)
  2. {
  3. string strjson = JsonConvert.SerializeObject(jwtdata);
  4. //主要的内容
  5. var claims = new Claim[]
  6. {
  7. //可以自定义键值对,也可以使用ClaimTypes里面定义的一些东西
  8. //这里设置Role,到后面验证token的时候,可以验证哪些Role可以通过鉴权
  9. new Claim("strKEY",strjson),
  10. new Claim(ClaimTypes.Role,"access")
  11. };
  12. //密钥,保存在配置文件的
  13. var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SecurityKey"]));
  14. //这是创建token
  15. var token = new JwtSecurityToken(
  16. issuer: "张三", //需要设置发布token的是谁
  17. audience: "李四", //接受验证token的是谁,这两一般是主机IP地址
  18. claims: claims, //这里是payload
  19. notBefore: DateTime.Now,
  20. expires: DateTime.Now.AddMinutes(5),//过期时间,一般授权token过期时间很短,只有几分钟,免得被人拿到了一直用
  21. signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) //使用签名算法
  22. );
  23. //最终生成了token字符串
  24. var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
  25. return jwtToken;
  26. }

Startup配置

ConfigureServices

  1. //添加jwt授权
  2. services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  3. .AddJwtBearer(options =>
  4. {
  5. options.TokenValidationParameters = new TokenValidationParameters
  6. {
  7. ValidateIssuerSigningKey = true,
  8. IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SecurityKey"])),
  9. ValidateIssuer = true,
  10. ValidIssuer = "张三",
  11. ValidateAudience = true,
  12. ValidAudience = "李四",
  13. ValidateLifetime = true,
  14. //大概相当于缓存时间,令牌时间+这里的时间=真正不能使用token的时间
  15. ClockSkew = TimeSpan.FromSeconds(20)
  16. };
  17. });

Configure

  1. //注意这两玩意的顺序,长得太像了
  2. app.UseAuthentication();
  3. app.UseAuthorization();

验证授权

在控制器上面加上一个特性。这个控制器的方法都要验证token,或者单独加在控制器方法上面,加了的验证,不加的不验证。

  1. //验证所有的token
  2. [Authorize]
  3. //指定设置了Role为access的token才能授权,没有设置Role不是access的哪怕token是对的,都无法鉴权,Role不对好像报405错误
  4. [Authorize(Roles = "access")]
  5. //不用token都可以访问,所有所有访问
  6. [AllowAnonymous]

刷新令牌

上面说了一个进入令牌几分钟就过期了。过期了就无法进入页面授权。短短的几分钟,特别影响体验。
但是进入授权令牌又不能设置时间太长,太长的话,要是被坏人拿到了就可以无限请求。如果时间很短,被坏人拿到了,能减少损失。

为了解决上面的问题。
我们在用户登录的时候给他发两个令牌,一个进入令牌,一个刷新令牌。

进入令牌:时间很短,每次请求都带上,进行鉴权。如果鉴权不通过就会报响应码401。过期了,token就失效了,同样的 不通过,也是401。但是不能说401就是过期了。
刷新令牌:时间长一点,每次请求当进入令牌失效了,就用刷新令牌发送一个请求到专门换令牌的接口,生成一个新的进入令牌替换原来的进入令牌。

比如:用户在页面填写表单,进入令牌过期了,他并不知道。当他点击提交,执行A方法,A方法发送数据到服务端,由于进入令牌过期了,报401错误,A方法里面的Ajax ,catch捕捉到401错误,就把拿出珍藏多时的刷新令牌,发送到一个指定的API接口,最好把过期的令牌一起发过去,或者发点别的东西过去。刷新令牌鉴权通过了,(最好再验证原来的令牌,或者接受的神秘参数正不正确之类的验证),验证刷新令牌成功后,服务端,再次生成一个进入令牌,返回。ajax的catch里面的ajax的请求返回结果如果成功了,拿到了新的进入令牌,就替换原来的进入令牌。然后调用A方法(A方法内部调用A方法),这时候在执行A方法,把数据发送到服务端,由于进入令牌被替换了新的,这次成功了。 反之如果刷新令牌都鉴权不通过,401,说明可能刷新令牌也过期了。刷新令牌都过期了,就重新回到登录页面,账号密码登录成功了就可以得到两个新的令牌了

如上的操作,用户丝毫感觉不到进入令牌过期,一气呵成。

刷新令牌和上面的生成操作一样,可能刷新令牌的payload没啥东西放,进入令牌的payload可以放点需要用到的参数。

  1. public string GenerateRefreshToken()
  2. {
  3. //主要的内容
  4. var claims = new Claim[]
  5. {
  6. new Claim(ClaimTypes.Role,"refresh")
  7. };
  8. //密钥
  9. var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SecurityKey"]));
  10. //这是创建token
  11. var token = new JwtSecurityToken(
  12. issuer: "张三",
  13. audience: "李四",
  14. claims: claims,
  15. notBefore: DateTime.Now,
  16. expires: DateTime.Now.AddMinutes(30),
  17. signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
  18. );
  19. //最终生成了token字符串
  20. var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
  21. return jwtToken;
  22. }

获取令牌里面payload包含的数据

  1. /// <summary>
  2. /// 得到token里面的内容
  3. /// </summary>
  4. public JWTData_json GetAccessPayload()
  5. {
  6. string token = _accessor.HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
  7. string payload = JwtPayload.Base64UrlDeserialize(token.Split(".")[1])["strKEY"].ToString();
  8. //还应该有个解密环节,暂留
  9. //序列化json
  10. return JsonConvert.DeserializeObject<JWTData_json>(payload);
  11. }

前端携带Token请求

发送请求的时候,设置请求头,KEY为Authorization ,VALUE为Bearer+token

Bearer后面要打一个空格,然后加上token字符串

参考

ASP.NET Core Web Api之JWT(一) ASP.NET Core Web Api之JWT VS Session VS Cookie(二) ASP.NET Core Web Api之JWT刷新Token(三) ASP.Net Core 3.1 中使用JWT认证