JWT(JSON Web 令牌)

JWT(JSON Web 令牌)

码农世界 2024-05-29 前端 91 次浏览 0个评论

参考文献

[2] JWT令牌-CSDN博客 参考日期:2024.05.21

[1] Day12-06. 登录校验-JWT令牌-介绍哔哩哔哩bilibili 参考日期:2024.05.21

JWT 是何

概念

JWT 是 JSON Web Token 的缩写,即 JSON Web 令牌,是一种用于在网络应用程序之间 以 JSON 数据格式安全传输信息 的开放标准(RFC 7519)

JWT 是一种 轻量级 的 Token,定义了一种简洁的、自包含的格式,包含了一些关键信息,比如用户身份、权限等

由于 数字签名 的存在,这些信息是可靠的,在传输过程中 不被篡改或伪造

使用 JWT 的好处:可以方便地在不同的应用程序或服务之间共享用户身份信息,而无需每次都进行身份验证。且因为 JWT 是基于标准的 JSON 格式,易于使用和传输

构成

JWT 的格式是由 RFC 7519 标准规定的三部分组成:

  • 头部(Header):包含 Token 类型、加密算法等信息

  • 载荷(Payload):包含 了需要传输的信息,分为 自定义信息 和 默认信息,例如用户 ID、角色、权限等

    可能称为 “携带” 更能体现 Payload 的字面意义

  • 签名(Signature):将 Header 和 Payload 加密 后得到的结果,用于验证 Token 是否被篡改,确保安全性

    加入指定秘钥,通过指定签名算法计算


    假设一个网站需要验证用户身份并授权用户访问某些受保护的资源,使用 JWT,用户登录后,服务器会生成一个包含用户信息的 JWT,然后将该 Token 返回给客户端

    如下所示:

     eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
     eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWQiOiIxMjM0NTY3ODkwIiwicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl19.
     SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

    不要被这一 “坨” 字符串吓到,这不是我们处理的,甚至无需去看,这是给计算机去处理的

    JWT 接收原始 JSON 格式后,会对其根据 Base64 进行编码处理,然后再进行解码

    Base64:是一种基于 64 个可打印字符(A-Z、a-z、0-9、+、-)还有一个补位字符 = 来表示二进制数据的编码方式

    其中,第 1 行是 Header;第 2-3 行是 Payload;第 3 行是 Signature。三部分之间用英文 . 分隔

    Header

    从 Header 开始分析,这 “坨” 字符串表示的原始 JSON 格式是:

     {"alg": "HS256", "type": "JWT"}

    参数含义:

    • alg:表示使用的签名算法,上例中指定为 HMAC-SHA256 算法

    • type:表示 Token 类型,上例中指定为 JWT

      关于签名算法可以前往 JWT 官网 查看:

      Payload

       {
        "sub": "1234567890",  // ID
        "name": "John Doe", // 角色
        "iat": 1516239022  // 权限
       }

      所有参数含义:

      • iss:JWT 签发者

        • sub:JWT 所面向的用户

          • aud:接收 JWT 的一方

          • iat:JWT 的签发时间

            • exp:JWT 的过期时间,这个过期时间必须要大于签发时间

              • nbf:在定义时间之前,该 JWT 不可用

                • jti:JWT 唯一身份标识,主要用来作为一次性 Token,从而回避重放攻击

                  Signature

                  Signature 的目的就是为了防止 Token 被篡改,确保 Token 的安全性

                  JWT 会根据依照 Header 中指定的 alg,即 “签名算法” 来合并前面的 Header 和 Payload,并且还会加入 我们个人自定义的密钥,然后再来编码 Signature

                  这个 Signature 是自动计算出来的,并不是根据 Base64 编码而成

                  一旦 Signature 中的某个字符被篡改,那整个 JWT 的校验都会失败。正是因为 Signature 的存在,才使得 JWT 非常安全可靠

                  JWT 如何

                  常用场景 - 登录校验

                  JWT 最常用的场景就是登录校验

                  登录流程:

                  1. 浏览器(前端)发起登录操作,此时访问登录接口(后端)

                  2. 登录校验成功,后端 生成 JWT

                  3. 后端将生成的 JWT 返回给前端

                  4. 前端拿到 JWT 后,会将其存储在浏览器的 localScorage 中,在 后续所有请求 中,每一次请求都会将这个 JWT 存储在请求头中随着请求发送到后端

                    local scorage 本地存储

                  5. 当前端再次发起请求时,后端会进行 统一拦截,判断这次请求中是否携带 JWT:

                    统一拦截就是过滤器和拦截器的知识点了,与 JWT 本身无关

                    • 没有携带 JWT:拒绝前端访问,请求响应失败

                      • 携带 JWT:校验 JWT 是否有效

                    • 前端请求携带的 JWT 有效,则后端放行,正常处理请求

                  后端主要操作就是 生成和校验 JWT

                  注意:

                  • 登录页面的请求不会拦截,因为后端需要根据登录信息生成 JWT

                  • 后端通过解析 JWT 并验证 Signature,可以获取到 Payload 部分存储的用户的身份和权限,从而判断是否放行

                    基于 Java 生成和校验 JWT

                    在项目中引入 JWT 库

                    想要使用 JWT,首先就要在项目中引入 JWT 的相关依赖

                    在实际使用中,通常会使用现有的 JWT 库来生成和解析 JWT,常用的 JWT 库有 java-jwt、jjwt

                    这些库已经实现了 RFC 7519 标准中的格式和规范,因此可以方便地使用这些库来生成和解析 JWT

                    下面以 jjwt 为例

                    在项目 pom.xml 中引入 jjwt 依赖:

                     
                     
                         io.jsonwebtoken
                         jjwt
                         0.9.1
                     

                    引入依赖后,就可以使用该工具包中的 API 来实现 JWT 的生成和校验

                    生成 JWT

                    以参考文献中黑马程序员 - Java Web 开发教程\day12-SpringBootWeb登录认证\代码\tlias-web-management 的项目为例

                    引入依赖后,打开测试类 TliasWebManagementApplicationTests.java

                    在测试类中添加测试生成 JWT 的方法:

                     @Test
                     public void testGenJWT(){
                         // 通过 Map 集合存储生成 JWT 的载荷
                         Map claims = new HashMap<>();
                         // 自定义信息(一般是存储前端请求携带的登录信息)
                         claims.put("id", 1);  // 用户 ID
                         claims.put("name", "Li");  // 用户名
                     ​
                         // 调用 Jwts.builder() 生成 JWT,使用链式方法设置生成 JWT 时需要的参数
                         String jwt = Jwts.builder()
                             // 设置签名算法为 HS256,密钥自定义
                             .signWith(SignatureAlgorithm.HS256, "LiiiYiAn")  
                             .setClaims(claims)  // 设置载荷
                             // 设置 JWT 有效期为 1h(以 ms 为单位)
                             .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))  
                             .compact();  // 返回生成 JWT 根据设置信息编码后的字符串
                     ​
                         System.out.println("生成的 JWT 为:" + jwt);
                     }

                    编写完成后,就可以测试运行这个方法了

                    这里有一个小技巧,当测试方法是独立的,即与整个 Spring 环境无关联,就可以先注释掉测试类上的注解 @SpringBootTest,这样就不会加载整个 Spring 环境,能 加快方法运行速度

                    运行后输出:

                     生成的 JWT 为:eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiTGkiLCJpZCI6MSwiZXhwIjoxNzE2MzEyOTg0fQ.Uhqi2bGY6SBjNVRto6nv-PbpFAg1ZFZi5HLKA4GLirE

                    此时可以复制这串字符串,然后打开 JWT 官网,将这串字符串放在 Encoded 框中:

                    此时会解析这串字符串,解析后就能得到 JWT 编码前的原始 JSON 格式信息了

                    参数解析:

                    • alg:签名算法

                    • exp:JWT 过期时间

                      因为 Signature 并不是 Base64 编码,是通过 Header + Payload + 签名算法计算得出的,所以不会解析出来

                      这是 JWT 的 “看家本领”,怎么能展示出来呢,如果解析出来了那别人岂不是能反推从而破解 JWT,那还谈什么传输信息安全性

                      也可以直接在网上搜索 Base64 编码解码工具,也能得到 Header 和 Payload 的解码原始 JSON 数据格式,比如 Base64 在线编码解码 | Base64 加密解密 - Base64.us

                      同样的,Signature 不能解码

                      复制 Header 部分进行解码:

                      解析 JWT(校验)

                      在测试类中添加测试解析 JWT 的方法:

                       @Test
                       public void testParseJWT(){
                           // 调用 Jwts.parser() 解析 JWT 中携带的载荷部分
                           // 使用链式方法设置解析 JWT 时需要的参数
                           Claims claims = Jwts.parser()
                               // 设置解析时的签名算法密钥,必须与生成 JWT 时设置的密钥一致否则解析失败
                               .setSigningKey("LiiiYiAn")
                               // 解析生成 JWT 根据设置信息编码后的字符串
                              .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiTGkiLCJpZCI6MSwiZXhwIjoxNzE2MzEyOTg0fQ.Uhqi2bGY6SBjNVRto6nv-PbpFAg1ZFZi5HLKA4GLirE")
                               // 返回解析 JWT 后载荷部分的原始 JSON 数据格式
                               // 也可以使用 Jwts.getHeader() 返回头部部分
                               // 不过前面的参数类型要改为 Header
                               .getBody();
                       ​
                           System.out.println(claims);
                       }

                      运行测试:

                      报错信息:

                       io.jsonwebtoken.ExpiredJwtException: JWT expired at 2024-05-22T01:36:24Z. Current time: 2024-05-22T10:33:24Z, a difference of 32220847 milliseconds.  Allowed clock skew: 0 milliseconds.
                       过期JwtException:JWT于2024-05-22T01:36:24Z过期。当前时间:2024-05-22T10:33:24Z,相差32220847毫秒。允许的时钟偏移:0毫秒

                      就是 JWT 过期了,所以无法解码

                      如果 JWT 解析校验时报错,则说明 JWT 被篡改或失效了,Token 非法

                      这是因为我使用的是前面生成的 JWT,而当时设置的 JWT 有效时间是 1h,而这次解析 JWT 已经过了 1h,所以 JWT 失效了

                      此时我们应该重新去运行前面生成 JWT 的方法,然后复制生成的 JWT,替换掉解析 JWT 中的 JWT 字符串:

                       .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiTGkiLCJpZCI6MSwiZXhwIjoxNzE2MzQ5MDQxfQ.4T3cw6vzdjQffK2vBrokxsX7-nPNuKFPol8jGlEVYxk")

                      再次运行:

                      解析 JWT 中的载荷部分成功

                      同时,一旦 JWT 生成了,我们篡改其中的任何一个字符都会解析失败,可以尝试修改后运行

转载请注明来自码农世界,本文标题:《JWT(JSON Web 令牌)》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,91人围观)参与讨论

还没有评论,来说两句吧...

Top