初识JWT令牌

54 阅读2分钟

JWT(JSON Web Token)就是一种“登录后发给客户端的身份凭证”,本质是一个字符串。后续每次请求客户端都带上它,服务端验证通过就认为“你已登录”。


1)JWT 是什么,有什么用

  • 解决问题:HTTP 无状态,服务端无法知道你之前是否登录

  • 做法:登录成功 → 服务端生成 JWT → 返回给前端 → 前端存起来(localStorage/APP存储)→ 之后请求都带 token

  • 优点

    • 跨域/跨端(PC、移动端)都好用
    • 支持集群(不依赖服务器内存 Session)
    • 服务端压力小(不需要保存会话数据)

2)JWT 的结构(必须记住)

JWT = Header.Payload.Signature(三段用 . 分割)

Header(头)

描述令牌类型、算法,比如:

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

Payload(载荷)

你放的数据(声明 claims),比如 id、username,以及系统字段 exp(过期时间)等。

注意:Payload 只是 Base64 编码,不是加密,别放密码/敏感信息。

Signature(签名)

密钥 + header + payload 按算法算出来的签名

  • 作用:防篡改
  • 任何一位改了,验签就失败

✅ 口诀:payload可看不可改,signature保证不被改


3)JWT 实现要做哪两件事

A. 生成 token(登录成功时)

  • 查询用户名密码正确
  • 组装 claims(比如 id/username)
  • 设置过期时间
  • 使用密钥签名 → 生成字符串 token

B. 校验 token(每次请求时)

  • 从请求头拿 token(比如 header: token 或标准 Authorization: Bearer xxx

  • 解析 token:

    • 能解析:合法(未过期、未篡改、密钥正确)
    • 解析报错:非法(过期/篡改/密钥不对)→ 返回 401

4)SpringBoot + jjwt 的典型实现

4.1 引入依赖

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.9.1</version>
</dependency>

4.2 JWT 工具类(生成 + 解析)

public class JwtUtils {
    private static String signKey = "SVRIRUlNQQ==";  // 签名密钥(自己换)
    private static Long expire = 12 * 3600 * 1000L;  // 12小时

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

    // 解析JWT(校验:签名+过期)
    public static Claims parseJWT(String jwt){
        return Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();
    }
}

4.3 登录成功后下发 token

public LoginInfo login(Emp emp) {
    Emp empLogin = empMapper.getUsernameAndPassword(emp);
    if(empLogin == null) return null;

    Map<String,Object> claims = new HashMap<>();
    claims.put("id", empLogin.getId());
    claims.put("username", empLogin.getUsername());

    String token = JwtUtils.generateJwt(claims);
    return new LoginInfo(empLogin.getId(), empLogin.getUsername(), empLogin.getName(), token);
}

4.4 统一拦截校验 token(拦截器示例)

@Component
public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
        String token = request.getHeader("token");
        if(token == null || token.isEmpty()){
            response.setStatus(401);
            return false;
        }
        try {
            JwtUtils.parseJWT(token); // 能解析就代表合法
            return true;
        } catch (Exception e) {
            response.setStatus(401);
            return false;
        }
    }
}

注册拦截器(放行登录接口):

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired private TokenInterceptor tokenInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/login");
    }
}

5)容易踩的坑(高频)

  1. 密钥不一致:生成用的 key 和解析用的 key 必须一样
  2. token 放错位置:后端从 header 取,前端要在请求头带上
  3. payload 放敏感信息:JWT 不是加密,别放密码/身份证等
  4. 过期处理:过期会解析失败,要返回 401,让前端跳登录
  5. 推荐标准请求头Authorization: Bearer <token>(更规范)