JWT,即 JSON Web Token,是一种基于 JSON 的开放标准(RFC 7519),主要用于在网络应用环境间安全地传递声明。这种声明被进行了数字签名,可以验证和信任,因此,它适用于各种需要信息安全性和无状态的应用。
在具体加密过程中,客户端会使用 RSA 算法生成 JWT 串,这里用到了私钥“加密”,而公钥是公开的,任何人都能解密,但内容无法变更。也就是说,在 JWT 中并没有纯粹的加密过程,而是通过加密保障了信息的完整性和真实性。
适用场景:
优点:
局限性:
下边是一个示例密文 token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IuW8oOS4iSIsImVtYWlsIjoiemhhbmdzYW5AZXhhbXBsZS5jb20iLCJ0ZW1wa2V5IjoidGVtcHZhbHVl5YC8IiwibmJmIjoxNzAyODg2MjgxLCJleHAiOjE3MDI4ODYyOTEsImlhdCI6MTcwMjg4NjI4MX0.2nuyYrAxVq3aAReN257eMHKGG44j5QyPMabxMnSzVBU
密文起始就是,看起来是非常复杂,实际上有章可循的,如下图,密文以其中的两个句点为分隔,可分为三个部分:Header、Payload、Signature。
Header 的主要作用是用来标识。通常是两部分组成:
alg 参数 JWT 官网提供了 12 种算法,如下图,但一般都采用 HS256。
明文示例:
{ "alg": "HS256", "typ": "JWT" }
经过 Base64Url 编码后,就是密文中第一部分的内容:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload 是 JWT 密文中的重要组成部分,也可称为 JWT claims,它包含了需要传递的数据信息,解密后的数据格式也是 Json。
claims 可以分为三种类型:registered(预定义声明)、public(公共声明)、private(私有声明)。
registered(预定义声明):不是强制性的,但推荐使用,以提供一组有用的、可互操作的声明。
其中主要包括四个:iss(issuer,发送数据人,标识发送 JWT 主体)、exp(expiration time,数据消息的过期时间,一般采用时间戳格式)、sub(subject,数据消息主题)、aud(audience,数据消息的接收者)。
public(公共声明):公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息。一般不建议添加敏感信息,因为该部分在任何客户端均可解密。
private(私有声明):私有声明是提供者和消费者所共同定义的声明。
注:claims 声明名称一般只有三个字符长,因为 JWT 的目的是精简。
示例:
{ "unique_name": "张三", "email": "zhangsan@example.com", "tempkey": "tempvalue值" }
经过 Base64Url 加密后,得到密文的第二部分内容:
eyJ1bmlxdWVfbmFtZSI6IuW8oOS4iSIsImVtYWlsIjoiemhhbmdzYW5AZXhhbXBsZS5jb20iLCJ0ZW1wa2V5IjoidGVtcHZhbHVl5YC8IiwibmJmIjoxNzAyODg2MjgxLCJleHAiOjE3MDI4ODYyOTEsImlhdCI6MTcwMjg4NjI4MX0
Signature 部分是对 Header 和 Payload 两部分的签名,作用是防止 JWT 被篡改。
要创建签名部分,前提是必须获取编码后的 Header、编码后的有效负载 Payload、secret 密钥、标头中 alg 指定的算法,一般为 HS256,然后才能对其进行签名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
加密后得到的密文就是 Token 中最后一部分内容。
密钥 secret 是保存在服务端的,服务端会根据这个密钥进行生成 token 和验证,所以需要严格保密。
JWT 经常用于身份验证流程,以下是一个简单的步骤:
另外,在日常业务中,有可能出现并发异常问题。
当服务端在检查到请求的令牌过期之后,会提示用户再次做登录操作。
这在流程上没什么问题,但在页面加载后,如果同一个页面中有多个异步请求同时触发,每一个请求都携带原始令牌,在这样的设计下,就有可能出现在第一个请求到达后刷新了 Token,并更改了缓存中数据的时间戳,以至于剩余请求校验时发现时间戳不一致导致验证失败。
同一时间触发的请求越多,抛出的异常也就越多。虽然第一个请求已经刷新了 Token,但是其余的请求是失败的,页面中的数据并不完整,显然这是不正常的。那如何避免呐?
redis 锁机制:在触发更新 Token 时,将同一用户信息加锁,使得此用户的其他请求均失败,待登录验证通过后再重新加载。
Token 定时刷新:当用户在线时,间隔一段时间刷新一次 Token。要刷新令牌,API 需要一个新的端点,它接收一个有效的,没有过期的 JWT,并返回与新的到期字段相同的签名的 JWT。若用户长时间没登录,则直接跳转到登录页。
直接看代码吧。
// 测试一下 class Program { static void Main(string[] args) { JwtTest jwtTest= new JwtTest(); string key = "keayvkkakeyvaluyeaeayvalalujeehayvalguaealrue"; var claims = new[] { new Claim(ClaimTypes.Name, "张三"), new Claim(ClaimTypes.Email, "zhangsan@example.com"), new Claim("tempkey", "tempvalue值"), }; string token = jwtTest.JwtEncode(key, claims); Console.WriteLine(token); // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IuW8oOS4iSIsImVtYWlsIjoiemhhbmdzYW5AZXhhbXBsZS5jb20iLCJ0ZW1wa2V5IjoidGVtcHZhbHVl5YC8IiwibmJmIjoxNzAzNTkxMTk4LCJleHAiOjE3MDM1OTEyMDgsImlhdCI6MTcwMzU5MTE5OH0.65Mx_ldbQijHevkalutHMaejQ06vhe5fW6e6-t-aziw string json = jwtTest.JwtDecode(token, key); // {"unique_name":"张三","email":"zhangsan@example.com","tempkey":"tempvalue值","nbf":1703591258,"exp":1703591268,"iat":1703591258} Console.WriteLine(json); } }
验证一下:https://jwt.io/。
// JWT 加密解密类 public class JwtTest { public string JwtEncode(string keyvalue, Claim[] claims) { var key = Encoding.UTF8.GetBytes(keyvalue); var tokenHandler = new JwtSecurityTokenHandler(); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = DateTime.UtcNow.AddSeconds(10), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); var tokenString = tokenHandler.WriteToken(token); Console.WriteLine($"加密后的JWT: {tokenString}"); return tokenString; } public string JwtDecode(string jwttoken, string publickey) { try { IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm); var json = decoder.Decode(jwttoken, publickey, verify: true); return json; } catch(Exception ex) { return ""; } } }
参考:https://jwt.io/introduction https://juejin.cn/post/7232550589964140602 https://cloud.tencent.com/developer/article/2148676