Markdown|SpringBoot 整合 JWT 实现无状态登录认证实战

举报
JiHua 发表于 2026/10/20 09:59:17 2026/10/20
【摘要】 传统基于Session的登录方案在分布式集群场景下存在会话共享困难的问题,JWT(JSON Web Token)作为无状态认证方案,无需在服务端存储会话信息,非常适合前后端分离项目。本文从零演示SpringBoot整合JJWT生成、解析、校验Token,封装工具类,实现登录签发令牌、拦截器校验请求身份,同时讲解JWT常见坑点:过期时间、签名泄露、Payload不要存放敏感信息,并给出完整可运行代码

前言

在前后端分离架构中,客户端和服务端跨域交互,原生Session会话同步会变得麻烦。当服务端部署多实例做负载均衡时,Session复制或者Session共享方案会增加系统复杂度。

JWT 采用无状态设计,认证信息全部加密放在Token字符串中,客户端拿到Token后,后续每次请求在请求头携带Token,服务端只需要校验签名合法性,不需要查询数据库/缓存获取会话。

JWT结构分为三部分:Header、Payload、Signature。

  • Header:算法和类型,一般使用HS256对称加密
  • Payload:载荷,存放非敏感自定义信息,如用户ID、用户名
  • Signature:签名,使用密钥对前两部分加密,防止Token被篡改

重要提醒:JWT只是编码,不是加密!Payload可以被解码,不能存放密码、手机号等敏感数据。

一、环境依赖

Maven pom.xml引入JJWT依赖

<!-- JWT -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

二、JWT工具类封装

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;

@Component
public class JwtUtil {

    // 密钥,配置文件中自定义,长度至少32位
    @Value("${jwt.secret}")
    private String secret;

    // token有效期,单位毫秒,这里2小时
    @Value("${jwt.expire}")
    private long expire;

    /**
     * 获取签名密钥
     */
    private SecretKey getSignKey() {
        return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 生成Token
     */
    public String generateToken(Long userId, String username) {
        Date now = new Date();
        Date expireDate = new Date(now.getTime() + expire);
        return Jwts.builder()
                .setSubject(username)
                .claim("userId", userId)
                .setIssuedAt(now)
                .setExpiration(expireDate)
                .signWith(getSignKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    /**
     * 解析Token,获取用户名
     */
    public String getUsernameByToken(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(getSignKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
        return claims.getSubject();
    }

    /**
     * 校验token是否有效
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                    .setSigningKey(getSignKey())
                    .build()
                    .parseClaimsJws(token);
            return true;
        } catch (ExpiredJwtException e) {
            System.out.println("Token已过期");
        } catch (MalformedJwtException e) {
            System.out.println("Token格式错误");
        } catch (SignatureException e) {
            System.out.println("签名校验失败,token被篡改");
        } catch (Exception e) {
            System.out.println("token校验异常");
        }
        return false;
    }
}

application.yml配置

jwt:
  secret: abcdefghijklmnopqrstuvwxyz123456
  expire: 7200000

三、登录Controller

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

@RestController
@RequestMapping("/auth")
public class LoginController {

    @Resource
    private JwtUtil jwtUtil;

    @PostMapping("/login")
    public String login(@RequestParam String username, @RequestParam String password){
        // 模拟数据库校验账号密码
        if("admin".equals(username) && "123456".equals(password)){
            String token = jwtUtil.generateToken(1L, username);
            return token;
        }
        return "账号密码错误";
    }

    @PostMapping("/test")
    public String test(){
        return "受保护接口,登录成功才能访问";
    }
}

四、自定义拦截器校验Token

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class JwtInterceptor implements HandlerInterceptor {

    @Resource
    private JwtUtil jwtUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");
        if(token == null || token.isEmpty()){
            response.getWriter().write("未携带Token,请登录");
            return false;
        }
        if(!jwtUtil.validateToken(token)){
            response.getWriter().write("Token非法或已过期");
            return false;
        }
        return true;
    }
}

注册拦截器,放行登录接口

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Resource
    private JwtInterceptor jwtInterceptor;

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

五、测试流程

  1. POST请求 /auth/login,传入账号admin,密码123456,获取返回token字符串
  2. 请求 /auth/test,请求头带上 token: xxx,成功访问
  3. 不带token、篡改token、过期token都会拦截返回提示

六、生产环境避坑要点

  1. JWT无状态,无法主动销毁Token。如果需要强制下线,可以搭配Redis黑名单机制,过期的token放入黑名单,拦截器优先校验黑名单。
  2. 密钥一定要妥善保管,不要硬编码在代码上传到Git仓库,推荐使用配置中心管理密钥。
  3. Payload只放业务标识字段,不要存储敏感信息。
  4. Token有效期不宜设置过长,业务上可增加刷新Token(RefreshToken)机制,减少频繁登录。
  5. HTTPS必须开启,防止Token在传输过程被劫持。

七、总结

JWT无状态认证方案,在前后端分离、微服务场景中使用广泛。本文完整实现了JWT工具类、登录签发Token、拦截器鉴权,代码开箱即用。
但JWT不是万能方案,它最大短板是不能主动失效,需要结合业务场景选择方案。如果系统大量用户需要强制下线,单纯JWT方案就不够,需要搭配Redis黑名单做增强。

全文大约1220字,直接复制粘贴到华为云博客发布即可。

要不要继续下一篇?可选方向:

  1. SpringBoot + 异步线程@Async实战
  2. MyBatis-Plus 逻辑删除 + 自动填充
  3. SpringBoot整合MinIO文件对象存储实战
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。