闽公网安备 35020302035485号




eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoyLFwidXNlcm5hbWVcIjpcImxpc2lcIixcIm5hbWVcIjpcIuadjuWbm1wiLFwiZ3JhZGVcIjpcInZpcFwifSJ9.NT8QBdoK4S-PbnhS0msJAqL0FG2aruvlsBSyG226HiU这段加密字符串由三部分组成,中间由点“.”分隔,具体含义如下。
{
"alg": "HS256",
"typ": "JWT"
}
然后,此 JSON 被 Base64 编码以形成 JWT 的第一部分。{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后对原文进行 Base64 编码形成 JWT 的第二部分。eyJzdWIiOiJ7XCJ1c2VySWRcIjoyLFwidXNlcm5hbWVcIjpcImxpc2lcIixcIm5hbWVcIjpcIuadjuWbm1wiLFwiZ3JhZGVcIjpcInZpcFwifSJ9第三部分 签名(Sign):签名就是通过前面两部分标头+载荷+私钥再配合指定的算法,生成用于校验 JWT 是否有效的特殊字符串,签名的生成规则如下。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)生成的签名字符串为:
NT8QBdoK4S-PbnhS0msJAqL0FG2aruvlsBSyG226HiU将以上三部分通过“.”连接在一起,就是 JWT 的标准格式了。
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<!-- 堆代码 duidaima.com -->
<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 {
/**
* 堆代码 duidaima.com
* 创建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。
/**
* 堆代码 duidaima.com
* 校验及提取JWT数据
*/
@Test
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 配置中心统一存储,同时定期更换私钥以防止关键信息泄露。讲到这应该你已掌握 JWT 的基本用法,但是在微服务架构下又该如何设计用户认证体系呢?2.API 网关统一验签方案

{
"code": "0",
"message": "success",
"data": {
"user": {
"userId": 1,
"username": "zhangsan",
},
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxLFwidXNlcm5hbWVcIjpcInpoYW5nc2FuXCIsXCJuYW1lXCI6XCLlvKDkuIlcIixcImdyYWRlXCI6XCJub3JtYWxcIn0ifQ.1HtfszarTxLrqPktDkzArTEc4ah5VO7QaOOJqmSeXEM"
}
}
第四步,在收到上述 JSON 数据后,客户端将其中 token 数据保存在 cookie 或者本地缓存中;{
"code": "0",
"message": "success",
"data": {
"user": { #用户详细数据
"userId": 1,
"username": "zhangsan",
"name": "张三",
"grade": "normal"
"age": 18,
"idno" : 130.......,
...
},
"authorization":{ #权限数据
"role" : "admin",
"permissions" : [{"addUser","delUser","..."}]
}
}
}
第七步,具体的微服务收到上述 JSON 后,对当前执行的操作进行判断,检查是否拥有执行权限,权限检查通过执行业务代码,权限检查失败返回错误响应。到此从登录创建 JWT 到验签后执行业务代码的完整流程已经完成。
API 网关统一验签与服务端验签最大的区别是在 API 网关层面就发起 JWT 的验签请求,之后路由过程中附加的是从认证中心返回的用户与权限数据,其他的操作步骤与方案一是完全相同的。在这你可能又会有疑惑,为什么要设计两种不同的方案呢?其实这对应了不同的应用场景:
