JWT介绍及使用


前言

本文将介绍一下JWT,希望看完对你有所帮助


一、JTW是什么?

JWT全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;
官网:https://jwt.io

JWT包含三部分数据:

  • Header:头部,通常头部有两部分信息:

    • 声明类型,这里是JWT 自描述信息

    我们会对头部进行base64编码,得到第一部分数据 base64编码和解码的

  • Payload:载荷,就是有效数据,一般包含下面信息:

    • 用户身份信息(注意,这里因为采用base64编码,可解码 是可逆的,因此不要存放敏感信息)
    • 注册声明:如token的签发时间,过期时间,签发人等 这部分内容 好比身份证的信息

    这部分也会采用base64编码,得到第二部分数据

  • Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的密钥(secret)(不要泄漏,最好周期性更换),通过加密算法(不可逆的)生成一个签名。用于验证整个数据完整和可靠性。

生成的数据格式:
在这里插入图片描述

二、JWT 实现无状态 Web 服务


1、什么是有状态

有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。

例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。

缺点是什么?

  • 服务端保存大量数据,增加服务端压力
  • 服务端保存用户状态,无法进行水平扩展
  • 客户端请求依赖服务端,多次请求必须访问同一台服务器

2、什么是无状态

服务器不需要记录客户端的状态信息,即:

  • 服务端不保存任何客户端请求者信息
  • 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

带来的好处是什么呢?

  • 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
  • 服务端的集群和状态对客户端透明
  • 服务端可以任意的迁移和伸缩
  • 减小服务端存储压力

3、如何实现无状态

无状态登录的流程:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
  • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
  • 以后每次请求,客户端都携带认证的token
  • 服务的对token进行解密,判断是否有效。

流程图:

​ 客户端请求登录,登录之后颁发凭证

整个登录过程中,最关键的点是什么?

token的安全性

token是识别客户端身份的唯一标示,如果加密不够严密,被人伪造那就完蛋了。

采用何种方式加密才是安全可靠的呢?

我们将采用:JWT + RSA非对称加密

4、JWT交互流程

流程图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-31aitn81-1670641363084)(assets\image-20210824192630038.png)]

步骤翻译:

  • 1、用户登录
  • 2、服务的认证,通过后根据secret生成token
  • 3、将生成的token返回给浏览器
  • 4、用户每次请求携带token
  • 5、服务端利用公钥解读jwt签名,判断签名有效后,从Payload中获取用户信息
  • 6、处理请求,返回响应结果

因为JWT签发的token中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,完全符合了Rest的无状态规范。

5、非对称加密

加密技术是对信息进行编码和解码的技术,编码是把原来可读信息(又称明文)译成代码形式(又称密文),其逆过程就是解码(解密),加密技术的要点是加密算法,加密算法可以分为三类:

  • 对称加密,如AES
    • 基本原理:将明文分成N个组,然后使用密钥对各个组进行加密,形成各自的密文,最后把所有的分组密文进行合并,形成最终的密文。
    • 优势:算法公开、计算量小、加密速度快、加密效率高
    • 缺陷:双方都使用同样密钥,安全性得不到保证
  • 非对称加密,如RSA
    • 基本原理:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端
      • 私钥加密,持有私钥或公钥才可以解密
      • 公钥加密,持有私钥才可解密
    • 优点:安全,难以破解
    • 缺点:算法比较耗时
  • 不可逆加密,如MD5,SHA
    • 基本原理:加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,无法根据密文推算出明文。

RSA算法历史:

1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字缩写:RSA

目前流行的还有oauth2

三、nimbus-jose-jwt 库


nimbus-jose-jwt、jose4j、java-jwt 和 jjwt 是几个 Java 中常见的操作 JWT 的库。就使用细节而言,nimbus-jos-jwt(和jose4j)要好于 java-jwt 和 jjwt 。
nimbus-jose-jwt 官网:https://connect2id.com/products/nimbus-jose-jwt
所需坐标

 <dependency>
        <groupId>com.nimbusds</groupId>
        <artifactId>nimbus-jose-jwt</artifactId>
        <version>9.11.1</version>
    </dependency>

1、 JWT 和 JWS

这里我们需要了解下 JWT、JWS、JWE 三者之间的关系:

  • JWT(JSON Web Token)指的是一种规范,这种规范允许我们使用 JWT 在两个组织之间传递安全可靠的信息。
  • JWS(JSON Web Signature)和 JWE(JSON Web Encryption)是 JWT 规范的两种不同实现,我们平时最常使用的实现就是 JWS 。

简单来说,JWT 和 JWS、JWE 类似于接口与实现类。由于,我们使用的是 JWS ,所以,后续内容中,就直接列举 JWS 相关类,不再细分 JWS 和 JWE 了,numbus-jose-jwt 中的 JWE 相关类和接口我们也不会使用到

2、加密算法

  • 对称加密』指的是使用相同的秘钥来进行加密和解密,如果你的秘钥不想暴露给解密方,考虑使用非对称加密。在加密方和解密方是同一个人(或利益关系紧密)的情况下可以使用它。
  • 非对称加密』指的是使用公钥和私钥来进行加密解密操作。对于加密操作,公钥负责加密,私钥负责解密,对于签名操作,私钥负责签名,公钥负责验证。非对称加密在 JWT 中的使用显然属于签名操作。在加密方和解密方是不同人(或不同利益方)的情况下可以使用它。

nimbus-jose-jwt 支持的算法都在它的 JWSAlgorithm 和 JWEAlgorithm 类中有定义。

例如:JWSAlgorithm algorithm = JWSAlgorithm.HS256

3、核心 API

a、加密过程

  • 在 nimbus-jose-jwt 中,使用 Header 类代表 JWT 的头部,不过,Header 类是一个抽象类,我们使用的是它的子类 JWSHeader

    创建头部对象:

@Test
 public void createToken(){
     //创建头部对象
     JWSHeader jwsHeader =
             new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法
                     .type(JOSEObjectType.JWT) // 静态常量
                     .build();
     System.out.println(jwsHeader);
 }
  • 你可以通过 .toBase64URL() 方法求得头部信息的 Base64 形式(这也是 JWT 中的实际头部信息):

  • 使用 Payload 类的代表 JWT 的荷载部分

    创建荷载部对象:

    @Test
      public void createToken(){
          //创建头部对象
          JWSHeader jwsHeader =
                  new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法
                          .type(JOSEObjectType.JWT) // 静态常量
                          .build();
          System.out.println(jwsHeader);
    
          //创建载荷
          Payload payload = new Payload("hello world");
          System.out.println(payload);
      }
    
  • 你可以通过 .toBase64URL() 方法求得荷载部信息的 Base64 形式(这也是 JWT 中的实际荷载部信息):

  • 签名部分

    ​ 签名部分没有专门的类表示,只有通用类 Base64URL ,而且签名部分并非你自己创建出来的,而是靠 头部 + 荷载部 + 加密算法 算出来的。在 nimbus-jose-jwt 中,签名算法由 JWSAlgorithm 表示。

    注意:在创建 JWSHeader 对象时就需要指定签名算法,因为在标准中,头部需要保存签名算法名字。

    ​ 用头部和荷载部分,再加上指定的签名算法和密钥来生成签名部分的过程,在 nimbus-jose-jwt 中被称为『签名(sign)』。nimbus-jose-jwt 专门提供了一个签名器 JWSSigner ,用来参与到签名过程中。密钥就是在创建签名器的时候指定的:

    JWSSigner jwsSigner = new MACSigner("密钥");  //MACSigner()中要指定一个密钥
    

    最终,整个 JWT 由一个 JWSObject 对象表示:

JWSObject jwsObject = new JWSObject(jwsHeader, payload);
// 进行签名(根据前两部分生成第三部分)
jwsObject.sign(jwsSigner);

在 nimbus-jose-jwt 中 JWSObject 是有状态的:未签名、已签名和签名中。很显然,在执行完 .sign() 方法之后,JWSObject 对象就变成了已签名状态。
当然,我们最终『要』的是 JWT 字符串,而不是对象,这里接着对代表 JWT 的 JWSObject 对象调用 .serialize() 方法即可:

String token = jwsObject.serialize();

完整示例:

  @Test
    public void createToken() throws JOSEException {

        //创建头部对象
        JWSHeader jwsHeader =
                new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法
                        .type(JOSEObjectType.JWT) // 静态常量
                        .build();
        //创建载荷
        Payload payload = new Payload("hello world");

        //创建签名器
        JWSSigner jwsSigner = new MACSigner("woniu");//woniu为密钥

        //创建签名
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);// 头部+载荷
        jwsObject.sign(jwsSigner);//再+签名部分

        //生成token字符串
        String token = jwsObject.serialize();
        System.out.println(token);
    }

如果出现:com.nimbusds.jose.KeyLengthException: The secret length must be at least 256 bits异常,是因为密钥的长度不够增加密钥长度即可
在这里插入图片描述

//创建签名器
JWSSigner jwsSigner = new MACSigner("woniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniu");

生成的token如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.aGVsbG8gd29ybGQ.p1E7jMNXs4zzCHDDbFzQXOko6s9NtT7Sqt15-T-7KVY

b、获得token中的内容

/**
     * 取出token中的内容
     */
    @Test
    public void getToken() throws IOException {
        //获得token头部内容
        String jwsHeader="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9";
        //反编码
        BASE64Decoder decoder=new BASE64Decoder();
        byte[] txtByte= jwsHeader.getBytes(StandardCharsets.UTF_8);
        System.out.println(new String(decoder.decodeBuffer(jwsHeader),"UTF-8"));

        //获得token载荷中的内容
        String payload="aGVsbG8gd29ybGQ";
        System.out.println(new String(decoder.decodeBuffer(payload),"UTF-8"));
    }

c、 解密

反向的解密和验证过程核心 API 就 2 个:JWSObject 的静态方法 parse 方法和验证其 JWSVerifier 对象。

JWSObject.parse() 方法是上面的 serialize 方法的反向操作,它可以通过一个 JWT 串生成 JWSObject 。有了 JWObject 之后,你就可以获得 header 和 payload 部分了。

如果你想直接验证 JWSObject 对象的合法性,你需要创建一个 JWSVerifier 对象。

//创建验证器
JWSVerifier jwsVerifier = new MACVerifier("密钥");//密钥要和加密时的相同

然后直接调用 jwsObject 对象的 verify 方法:

if (!jwsObject.verify(jwsVerifier)) {
    throw new RuntimeException("token 签名不合法!");
}

案例二:验证token是否合法

 /**
     * 验证token
     */
    @Test
    public void validationToken() throws Exception {
        //获得token
        String token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.aGVsbG8gd29ybGQ.p1E7jMNXs4zzCHDDbFzQXOko6s9NtT7Sqt15-T-7KVYA";
        //创建验证器
        JWSVerifier jwsVerifier = new MACVerifier("woniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniuwoniu");

        //获得签名对象
        JWSObject jwsObject=JWSObject.parse(token);
        //使用jws较验器,验证用户传递token是否合法
        if (!jwsObject.verify(jwsVerifier)) {
            throw new RuntimeException("token 签名不合法!");
        }
    }

4、创建jwt工具类

/**
 * jwt工具类
 */
@Component
public class JwtUtils {
    //使用uuid生成密钥
    private  final String secret= UUID.randomUUID().toString();
    //用户数据的key
    private  final String usernameKey="usernameKey";

    /**
     * 生成token
     * @param username 用户名
     * @return
     */
    public  String createJwtToken(String username) throws Exception {
        //创建头部对象
        JWSHeader jwsHeader =
                new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法
                        .type(JOSEObjectType.JWT) // 静态常量
                        .build();

        //创建载荷
        Map<String,Object> map=new HashMap<String,Object>();
        map.put(usernameKey, username);
        Payload payload= new Payload(map);

        //创建签名器
        JWSSigner jwsSigner = new MACSigner(secret);//密钥

        //创建签名
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);// 头部+载荷
        jwsObject.sign(jwsSigner);//再+签名部分

        //生成token字符串
        return jwsObject.serialize();
    }

    /**
     * 验证jwt token是否合法
     * @param jwtStr
     * @return
     */
    @SneakyThrows
    public  boolean verify(String jwtStr) {
        JWSObject jwsObject=JWSObject.parse(jwtStr);
        JWSVerifier jwsVerifier=new MACVerifier(secret);
        return jwsObject.verify(jwsVerifier);
    }

    /**
     * 从token中解析出用户名
     * @param jwtStr
     * @return
     */
    @SneakyThrows
    public  String getUserNameFormJwt(String jwtStr){
        JWSObject jwsObject=JWSObject.parse(jwtStr);
        Map<String,Object> map=jwsObject.getPayload().toJSONObject();
        return (String) map.get(usernameKey);
    }
}
<think>嗯,用户之前问过Java毕业设计涉及的技术,现在接着问JWT的详细介绍。我需要先理解JWT是什么,以及它在Java项目中的应用场景。用户可能是在做毕业设计时遇到了身份验证的问题,或者想了解如何安全地管理用户会话。 JWT的全称是JSON Web Token,应该先解释它的基本概念,结构组成,比如header、payload、signature。然后要说明它的工作原理,用户可能想知道为什么使用JWT而不是传统的session方式,所以需要比较两者的优缺点,比如无状态、跨域支持这些。 用户可能关心JWT的安全性,所以需要提到如何防止被篡改,比如签名机制,以及存储时需要注意的问题,比如不要放在localStorage中,可能涉及XSS和CSRF攻击的防范。另外,JWT的过期时间设置也很重要,避免令牌被长期滥用。 在Java使用JWT的话,可能需要推荐一些库,比如jjwt,然后给出一个简单的示例代码,说明如何生成和验证Token。这部分需要确保代码正确,并且符合Java的常见实践,比如使用Spring Security整合。 还要考虑用户可能的误区,比如JWT的存储位置,是否需要服务器端保存,以及注销和续签的问题。这些都是实际开发中常见的问题,需要详细解释解决方案,比如黑名单或者使用refresh token机制。 最后,结合之前用户的问题,JWT通常是毕业设计中实现用户认证的一部分,可以联系到之前提到的技术栈,比如Spring BootSpring Security,以及Redis用来处理黑名单。这样帮助用户将JWT整合到他们的项目架构中,确保他们理解如何在实际项目中应用。</think>以下是对JWT的详细介绍,从原理到应用全面解析: --- ### 一、JWT基础概念 **JWT(JSON Web Token)** 是一种开放标准(RFC 7519),用于在各方之间安全传输JSON格式的信息。主要应用场景: - 身份验证(最常见的用途) - 安全信息交换 - 无状态API授权 ### 二、JWT结构解析 一个标准JWT由三部分组成,格式为: $$ \text{Header.Payload.Signature} $$ 例如: ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ``` #### 1. Header(头部) - 声明令牌类型和签名算法 - Base64Url编码后的JSON ```json { "alg": "HS256", // 签名算法(如HS256/RSA) "typ": "JWT" // 令牌类型 } ``` #### 2. Payload(载荷) - 包含声明(claims)的JSON对象 - 标准声明(建议但不强制使用): ```json { "sub": "1234567890", // 主题(用户ID) "name": "John Doe", // 自定义声明 "iat": 1516239022, // 签发时间(时间戳) "exp": 1516239122 // 过期时间 } ``` - **三类声明**: - Registered claims:预定义标准字段(如iss, exp, sub等) - Public claims:公开定义的字段 - Private claims:双方协商的自定义字段 #### 3. Signature(签名) - 防止数据篡改的核心安全机制 - 计算公式: $$ \text{签名} = \text{HMAC\_SHA256}( \text{base64UrlEncode(header)} + "." + \text{base64UrlEncode(payload)}, \text{secretKey} ) $$ ### 三、JWT工作流程 ```mermaid sequenceDiagram 用户->>客户端: 提交登录凭证 客户端->>服务器: 发送用户名/密码 服务器->>服务器: 验证凭证有效性 服务器->>客户端: 返回签名的JWT 客户端->>服务器: 后续请求携带JWT(Header中) 服务器->>服务器: 验证签名+检查过期时间 服务器->>客户端: 返回请求数据 ``` ### 四、JWT核心特性 #### 优点: 1. **无状态**:服务端无需存储会话信息 2. **跨域支持**:适合微服务和分布式系统 3. **自包含**:payload可携带用户基本信息 4. **防篡改**:签名机制保证数据完整性 #### 缺点: 1. **无法废止**:签发后有效期内始终有效(需配合黑名单机制) 2. **payload不宜过大**:增加网络传输负担 3. **密钥管理**:密钥泄露会导致系统安全崩溃 ### 五、Java中的JWT实现 #### 常用库: - **jjwt**(Java JWT):最流行的实现 - **auth0/java-jwt**:功能全面的开源库 - **Nimbus JOSE+JWT**:符合JOSE规范 #### Spring Boot整合示例(使用jjwt): ```java // 生成Token public String createToken(String username) { return Jwts.builder() .setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + 3600_000)) // 1小时 .signWith(SignatureAlgorithm.HS512, "yourSecretKey") .compact(); } // 验证Token public Claims parseToken(String token) { return Jwts.parser() .setSigningKey("yourSecretKey") .parseClaimsJws(token) .getBody(); } ``` ### 六、安全实践建议 1. **密钥管理**: - 使用至少256位的强密钥 - 密钥存储在环境变量/配置中心,避免硬编码 2. **传输安全**: - 必须通过HTTPS传输 - 建议存放在HttpOnly Cookie中(防XSS攻击) 3. **时效控制**: - 设置合理过期时间(通常15分钟-2小时) - 使用refresh token机制续期 4. **敏感数据**: - 不要在payload中存储密码等敏感信息 - 避免暴露用户隐私数据 ### 七、JWT vs Session 对比 | 特性 | JWT | Session | |---------------------|------------------------------|-----------------------| | 存储位置 | 客户端 | 服务端 | | 扩展性 | 天然支持分布式系统 | 需要会话共享方案 | | 安全性 | 依赖签名算法和密钥管理 | 依赖Cookie安全配置 | | 性能影响 | 需解析签名(计算开销) | 需查询会话存储 | | 失效机制 | 需结合黑名单或短过期时间 | 直接删除会话即可 | ### 八、常见问题解决方案 1. **注销问题**: - 方案1:使用Token黑名单(Redis存储失效Token) - 方案2:设置较短过期时间 + 使用refresh token 2. **密钥轮换**: - 定期更新签名密钥 - 新旧密钥并存过渡期 3. **跨域共享**: - 配置CORS策略 - 确保Domain属性正确设置 --- **实际应用建议**:在毕业设计中,推荐将JWTSpring Security结合使用,配合Redis实现黑名单管理。典型流程可参考: $$ \text{用户登录} \rightarrow \text{签发JWT} \rightarrow \text{API鉴权} \rightarrow \text{权限控制} $$ 注意控制Payload体积,建议只存储用户ID和必要角色信息。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值