SpringBoot 整合 JWT——Token 认证与自动刷新

Session 方式在分布式环境下不好用,JWT(JSON Web Token)是目前主流的无状态认证方案。服务端不需要存 Session,Token 中包含了用户信息。

一、JWT 结构

header.payload.signature

header: 加密算法
payload: 用户数据(不要放密码)
signature: 签名,防止篡改

二、JWT 工具类

@Component
public class JwtUtil {

    @Value("${jwt.secret:mySecretKey}")
    private String secret;

    @Value("${jwt.expire:86400000}")
    private long expire;  // 默认24小时

    public String generateToken(Long userId, String username) {
        Date now = new Date();
        return Jwts.builder()
            .setSubject(userId.toString())
            .claim("username", username)
            .setIssuedAt(now)
            .setExpiration(new Date(now.getTime() + expire))
            .signWith(SignatureAlgorithm.HS256, secret)
            .compact();
    }

    public Claims parseToken(String token) {
        return Jwts.parser()
            .setSigningKey(secret)
            .parseClaimsJws(token)
            .getBody();
    }

    public boolean validateToken(String token) {
        try {
            parseToken(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

三、登录接口

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

    @Autowired
    private JwtUtil jwtUtil;

    @PostMapping("/login")
    public ResultVO<Map<String, Object>> login(
            @RequestParam String username,
            @RequestParam String password) {
        // 校验用户名密码
        if (!"admin".equals(username) || !"123456".equals(password)) {
            return ResultVO.error(401, "用户名或密码错误");
        }

        // 生成 Token
        String accessToken = jwtUtil.generateToken(1001L, username);
        String refreshToken = jwtUtil.generateRefreshToken(1001L);

        Map<String, Object> result = new HashMap<>();
        result.put("accessToken", accessToken);
        result.put("refreshToken", refreshToken);
        result.put("expiresIn", 86400);

        return ResultVO.success(result);
    }
}

四、Token 刷新

@PostMapping("/refresh")
public ResultVO<Map<String, Object>> refresh(
        @RequestParam String refreshToken) {
    if (!jwtUtil.validateToken(refreshToken)) {
        return ResultVO.error(401, "RefreshToken 无效");
    }

    Claims claims = jwtUtil.parseToken(refreshToken);
    Long userId = Long.parseLong(claims.getSubject());
    String username = claims.get("username", String.class);

    String newAccessToken = jwtUtil.generateToken(userId, username);
    String newRefreshToken = jwtUtil.generateRefreshToken(userId);

    return ResultVO.success(Map.of(
        "accessToken", newAccessToken,
        "refreshToken", newRefreshToken
    ));
}

五、拦截器校验

@Component
public class JwtInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            throw new BusinessException(401, "未登录");
        }

        token = token.substring(7);
        if (!jwtUtil.validateToken(token)) {
            throw new BusinessException(401, "Token 已过期");
        }

        // 将用户信息存入请求上下文
        Claims claims = jwtUtil.parseToken(token);
        request.setAttribute("userId", claims.getSubject());
        request.setAttribute("username", claims.get("username"));

        return true;
    }
}

六、前端调用

// 登录
const resp = await fetch('/auth/login', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({username: 'admin', password: '123456'})
});
const { accessToken, refreshToken } = await resp.json();

// 后续请求携带 Token
fetch('/api/user', {
    headers: {'Authorization': 'Bearer ' + accessToken}
});

// Token 过期时用 refreshToken 刷新
async function refreshAccessToken() {
    const resp = await fetch('/auth/refresh', {
        method: 'POST',
        body: new URLSearchParams({refreshToken})
    });
    const data = await resp.json();
    accessToken = data.accessToken;
}

七、安全注意事项

// 1. JWT 中不要放敏感信息
Claims claims = Jwts.parser()
    .setSigningKey(secret)
    .parseClaimsJws(token)
    .getBody();
String password = claims.get("password");  // ❌ 错误!

// 2. 密钥不要写死,用配置中心或环境变量
@Value("${jwt.secret}")
private String secret;

// 3. accessToken 有效期短(15-30分钟)
// refreshToken 有效期长(7天),专门用来刷新 accessToken

总结

Session 认证:服务端存 Session,分布式下需要共享 Session
JWT 认证: 无状态,Token 携带用户信息,适合分布式

JWT 缺点:签发后无法主动失效(只能等过期)
解决方案:accessToken 短时效 + refreshToken 刷新机制

💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值