Skip to content

JWT令牌


基本介绍

JWT 全称 JSON Web Token (官网:https://jwt.io),定义了一种简洁的、自包含的格式,用于在通信双方以 json 数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的

简洁:是指 jwt 就是一个简单的字符串。可以在请求参数或者是请求头当中直接传递

自包含:指的是 jwt 令牌,看似是一个随机的字符串,但是我们是可以根据自身的需求在 jwt 令牌中存储自定义的数据内容。如:可以直接在 jwt 令牌中存储用户的相关信息

简单来讲,jwt 就是将原始的 json 数据格式进行了安全的封装,这样就可以直接基于 jwt 在通信双方安全的进行信息传输了

令牌的组成

JWT 令牌由三个部分组成,三个部分之间使用英文的点来分割

第一部分:Header(头), 记录令牌类型、签名算法等。 例如:{"alg":"HS256","type":"JWT"}

第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。 例如:{"id":"1","username":"Tom"}

第三部分:Signature(签名),防止 Token 被篡改、确保安全性。将 header、payload,并加入指定秘钥,通过指定签名算法计算而来

签名的目的就是为了防 jwt 令牌被篡改,而正是因为 jwt 令牌最后一个部分数字签名的存在,所以整个 jwt 令牌是非常安全可靠的。一旦 jwt 令牌当中任何一个部分、任何一个字符被篡改了,整个令牌在校验的时候都会失败,所以它是非常安全可靠的。

引入 JWT 依赖

在 pom.xml 文件中引入 JWT 依赖

xml
<!-- JWT依赖-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

生成 JWT 令牌

java
@Test
public void testGenJwt() {
    Map<String, Object> claims = new HashMap<>();
    claims.put("id", 10);
    claims.put("username", "itheima");

    String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "aXRjYXN0") // 指定加密算法,密钥
        .addClaims(claims) // 添加自定义信息
        .setExpiration(new Date(System.currentTimeMillis() + 12 * 3600 * 1000)) // 令牌时效
        .compact(); // 生成令牌

    System.out.println(jwt);
}

生成结果

eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjcyNzI5NzMwfQ.fHi0Ub8npbyt71UqLXDdLyipptLgxBUg_mSuGJtXtBk

输出的结果就是生成的 JWT 令牌,,通过英文的点分割对三个部分进行分割,我们可以将生成的令牌复制一下,然后打开 JWT 的官网,将生成的令牌直接放在 Encoded 位置,此时就会自动的将令牌解析出来


第一部分解析出来,看到 JSON 格式的原始数据,所使用的签名算法为 HS256

第二个部分是我们自定义的数据,之前我们自定义的数据就是 id,还有一个 exp 代表的是我们所设置的过期时间

由于前两个部分base64 编码,所以是可以直接解码出来。但最后一个部分并不是 base64 编码,是经过签名算法计算出来的,所以最后一个部分(Signature)是不会解析的

解析 JWT 令牌

⚠️ 注意点:篡改令牌中的任何一个字符,在对令牌进行解析时都会报错,所以 JWT 令牌是非常安全可靠的

java
@Test
public void testParseJwt() {
    Claims claims = Jwts.parser().setSigningKey("aXRjYXN0")
        .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MTAsInVzZXJuYW1lIjoiaXRoZWltYSIsImV4cCI6MTcwMTkwOTAxNX0.N-MD6DmoeIIY5lB5z73UFLN9u7veppx1K5_N_jS9Yko")
        .getBody();
    System.out.println(claims);
}

解析结果

json
{id=10, username=itheima, exp=1701909015}

⚠️ 注意事项

(1)JWT 校验时使用的签名秘钥,必须和生成 JWT 令牌时使用的秘钥是配套的

(2)如果 JWT 令牌解析校验时报错,则说明 JWT 令牌被篡改 或 失效了,令牌非法

案例集成

引入 JWT 工具类

在 utils 包下创建 JwtUtils 类

java
package com.itheima.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.Map;

public class JwtUtils {

    private static String signKey = "SVRIRUlNQQ==";
    private static Long expire = 43200000L;

    /**
     * 生成JWT令牌
     * @return
     */
    public static String generateJwt(Map<String,Object> claims){
        String jwt = Jwts.builder()
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return jwt;
    }

    /**
     * 解析JWT令牌
     * @param jwt JWT令牌
     * @return JWT第二部分负载 payload 中存储的内容
     */
    public static Claims parseJWT(String jwt){
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();
        return claims;
    }
}

EmpServiceImpl

自定义信息以 Map 集合的形式进行存储

java
@Override
public LoginInfo login(Emp emp) {
    Emp empLogin = empMapper.getUsernameAndPassword(emp);
    if(empLogin != null){
        //1. 生成JWT令牌
        Map<String,Object> dataMap = new HashMap<>();
        dataMap.put("id", empLogin.getId());
        dataMap.put("username", empLogin.getUsername());

        String jwt = JwtUtils.generateJwt(dataMap);
        LoginInfo loginInfo = new LoginInfo(empLogin.getId(), empLogin.getUsername(), empLogin.getName(), jwt);
        return loginInfo;
    }
    return null;
}

实现过程

登录请求完成后,可以看到 JWT 令牌已经响应给了前端,此时前端就会将 JWT 令牌存储在浏览器本地

在当前案例中,JWT 令牌存储在浏览器的本地存储空间 localstorage 中了。 localstorage 是浏览器的本地存储,在移动端也是支持的

我们在发起一个查询部门数据的请求,此时我们可以看到在请求头中包含一个 token(JWT 令牌),后续的每一次请求当中,都会将这个令牌携带到服务端