转载 

Json Web Token(JWT)介绍及创建与校验示例代码Demo

分类:java,架构    1307人阅读    IT小君  2021-09-12 13:34

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 生成过程。

 
复制代码
  1. @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 字符串如下:

 
复制代码
  1. eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxMjN9In0.1p_VTN46sukRJTYFxUg93CmfR3nJZRBm99ZK0e3d9Hw
  • 第三步,验签代码,从 JWT 中提取 123 号用户数据。这里要保证 JWT 字符串、key 私钥与生成时保持一致。否则就会抛出验签失败 JwtException。

 
复制代码
  1. /**
  2. * 校验及提取JWT数据
  3. */
  4. @Test
  5. public void checkJwt(){
  6. String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxMjN9In0.1p_VTN46sukRJTYFxUg93CmfR3nJZRBm99ZK0e3d9Hw";
  7. //私钥
  8. String key = "1234567890_1234567890_1234567890";
  9. //1.对秘钥做BASE64编码
  10. String base64 = new BASE64Encoder().encode(key.getBytes());
  11. //2.生成秘钥对象,会根据base64长度自动选择相应的 HMAC 算法
  12. SecretKey secretKey = Keys.hmacShaKeyFor(base64.getBytes());
  13. //3.验证Token
  14. try {
  15. //生成JWT解析器
  16. JwtParser parser = Jwts.parserBuilder().setSigningKey(secretKey).build();
  17. //解析JWT
  18. Jws<Claims> claimsJws = parser.parseClaimsJws(jwt);
  19. //得到载荷中的用户数据
  20. String subject = claimsJws.getBody().getSubject();
  21. System.out.println(subject);
  22. }catch (JwtException e){
  23. //所有关于Jwt校验的异常都继承自JwtException
  24. System.out.println("Jwt校验失败");
  25. e.printStackTrace();
  26. }
  27. }

运行结果如下:

 
复制代码
  1. {"userId":123}

以上便是 JWT 的生成与校验代码,你会发现在加解密过程中,服务器私钥 key 是保障 JWT 安全的命脉。对于这个私钥在生产环境它不能写死在代码中,而是加密后保存在 Nacos 配置中心统一存储,同时定期更换私钥以防止关键信息泄露。

支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者

 工具推荐 更多»