JWT格式详解
无论是微服务架构,还是前后端分离应用,在客户端存储并加密数据时有一个通用的方案:Json Web Token(JWT),JWT是一个经过加密的,包含用户信息的且具有时效性的固定格式字符串。下面这是一个标准的JWT字符串。
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoyLFwidXNlcm5hbWVcIjpcImxpc2lcIixcIm5hbWVcIjpcIuadjuWbm1wiLFwiZ3JhZGVcIjpcInZpcFwifSJ9.NT8QBdoK4S-PbnhS0msJAqL0FG2aruvlsBSyG226HiU
这段加密字符串由三部分组成,中间由点“.”分隔,具体含义如下。
第一部分 标头(Header):标头通常由两部分组成:令牌的类型(即 JWT)和所使用的签名算法,例如 HMAC SHA256 或 RSA,下面是标头的原文:
{
"alg": "HS256",
"typ": "JWT"
}
然后,此 JSON 被 Base64 编码以形成 JWT 的第一部分。
eyJhbGciOiJIUzI1NiJ9
第二部分 载荷(Payload):载荷就是实际的用户数据以及其他自定义数据。载荷原文如下所示。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后对原文进行 Base64 编码形成 JWT 的第二部分。
eyJzdWIiOiJ7XCJ1c2VySWRcIjoyLFwidXNlcm5hbWVcIjpcImxpc2lcIixcIm5hbWVcIjpcIuadjuWbm1wiLFwiZ3JhZGVcIjpcInZpcFwifSJ9
第三部分 签名(Sign):签名就是通过前面两部分标头+载荷+私钥再配合指定的算法,生成用于校验 JWT 是否有效的特殊字符串,签名的生成规则如下。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
生成的签名字符串为:
NT8QBdoK4S-PbnhS0msJAqL0FG2aruvlsBSyG226HiU
将以上三部分通过“.”连接在一起,就是 JWT 的标准格式了。
JWT 的创建与校验
此时,你肯定有疑问 JWT 是如何生成的,又是如何完成有效性校验呢?因为 JWT 的格式与算法是固定的,在 Java 就有非常多的优秀开源项目帮我们实现了JWT 的创建与验签,其中最具代表性的产品就是 JJWT。JJWT 是一个提供端到端的 JWT 创建和验证的 Java 库,它的官网是:https://github.com/jwtk/jjwt,有兴趣的话你可以到官网阅读它的源码。
JJWT 的使用是非常简单的,下面我们用代码进行说明,关键代码我已做好注释。
第一步,pom.xml 引入 JJWT 的 Maven 依赖。
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
-
第二步,编写创建 JWT 的测试用例,模拟真实环境 UserID 为 123 号的用户登录后的 JWT 生成过程。
-
@SpringBootTest public class JwtTestor { /** * 创建Token */ @Test public void createJwt(){ //私钥字符串 String key = "1234567890_1234567890_1234567890"; //1.对秘钥做BASE64编码 String base64 = new BASE64Encoder().encode(key.getBytes()); //2.生成秘钥对象,会根据base64长度自动选择相应的 HMAC 算法 SecretKey secretKey = Keys.hmacShaKeyFor(base64.getBytes()); //3.利用JJWT生成Token String data = "{\"userId\":123}"; //载荷数据 String jwt = Jwts.builder().setSubject(data).signWith(secretKey).compact(); System.out.println(jwt); } }
运行结果产生 JWT 字符串如下:
-
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxMjN9In0.1p_VTN46sukRJTYFxUg93CmfR3nJZRBm99ZK0e3d9Hw
-
第三步,验签代码,从 JWT 中提取 123 号用户数据。这里要保证 JWT 字符串、key 私钥与生成时保持一致。否则就会抛出验签失败 JwtException。
-
/**
-
* 校验及提取JWT数据
-
*/
-
-
public void checkJwt(){
-
String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxMjN9In0.1p_VTN46sukRJTYFxUg93CmfR3nJZRBm99ZK0e3d9Hw";
-
//私钥
-
String key = "1234567890_1234567890_1234567890";
-
//1.对秘钥做BASE64编码
-
String base64 = new BASE64Encoder().encode(key.getBytes());
-
//2.生成秘钥对象,会根据base64长度自动选择相应的 HMAC 算法
-
SecretKey secretKey = Keys.hmacShaKeyFor(base64.getBytes());
-
//3.验证Token
-
try {
-
//生成JWT解析器
-
JwtParser parser = Jwts.parserBuilder().setSigningKey(secretKey).build();
-
//解析JWT
-
Jws<Claims> claimsJws = parser.parseClaimsJws(jwt);
-
//得到载荷中的用户数据
-
String subject = claimsJws.getBody().getSubject();
-
System.out.println(subject);
-
}catch (JwtException e){
-
//所有关于Jwt校验的异常都继承自JwtException
-
System.out.println("Jwt校验失败");
-
e.printStackTrace();
-
}
-
}
运行结果如下:
-
{"userId":123}
以上便是 JWT 的生成与校验代码,你会发现在加解密过程中,服务器私钥 key 是保障 JWT 安全的命脉。对于这个私钥在生产环境它不能写死在代码中,而是加密后保存在 Nacos 配置中心统一存储,同时定期更换私钥以防止关键信息泄露。