Spring 5.x + 老项目的 JWT 拦截器 + 自动续期

最终完整代码:JWT 拦截器 + 自动续期

1. 先给你 续期工具方法(加到你的 JwtUtil 里)

java
 
运行
 
 
 
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtUtil {

    private static final String SECRET = "12345678901234567890123456789012";
    private static final Key KEY = Keys.hmacShaKeyFor(SECRET.getBytes());
    private static final long EXPIRATION = 7200000; // 2小时
    private static final long REFRESH_BEFORE = 10 * 60 * 1000; // 剩余<10分钟就续期

    // 创建Token
    public static String createToken(String userId, String username) {
        Map<String, Object> map = new HashMap<>();
        map.put("userId", userId);
        map.put("username", username);
        return Jwts.builder()
                .setClaims(map)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
                .signWith(KEY)
                .compact();
    }

    // 解析
    public static Claims getClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(KEY)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    // ====================== 【自动续期核心方法】 ======================
    // 判断是否需要刷新
    public static boolean needRefresh(String token) {
        try {
            Claims claims = getClaims(token);
            long expire = claims.getExpiration().getTime();
            long now = System.currentTimeMillis();
            return (expire - now) < REFRESH_BEFORE;
        } catch (Exception e) {
            return false;
        }
    }

    // 刷新Token(用旧信息生成新Token)
    public static String refreshToken(String token) {
        Claims claims = getClaims(token);
        String userId = (String) claims.get("userId");
        String username = claims.getSubject();
        return createToken(userId, username);
    }

    // 获取用户ID
    public static String getUserId(String token) {
        return (String) getClaims(token).get("userId");
    }
}
 

2. Spring MVC 拦截器(自动续期 + 自动返回新 Token)

java
 
运行
 
 
 
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class JwtRefreshInterceptor implements HandlerInterceptor {

    // 前端传过来的 header 名称
    private static final String TOKEN_HEADER = "Authorization";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = request.getHeader(TOKEN_HEADER);

        // 没有token → 放行(登录接口等)
        if (token == null || token.isEmpty()) {
            return true;
        }

        try {
            // 1. 先验证token是否合法
            String userId = JwtUtil.getUserId(token);
            request.setAttribute("userId", userId);

            // 2. 判断是否需要续期(剩余<10分钟)
            if (JwtUtil.needRefresh(token)) {
                String newToken = JwtUtil.refreshToken(token);
                System.out.println("自动续期新Token:" + newToken);

                // 把新Token放回响应头,前端自动替换即可
                response.setHeader("Authorization", newToken);
                response.setHeader("Access-Control-Expose-Headers", "Authorization");
            }

            return true;

        } catch (ExpiredJwtException e) {
            response.setStatus(401);
            return false;
        } catch (Exception e) {
            response.setStatus(401);
            return false;
        }
    }
}
 

3. 注册拦截器(Spring 5.x)

java
 
运行
 
 
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private JwtRefreshInterceptor jwtRefreshInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtRefreshInterceptor)
                .addPathPatterns("/api/**")        // 你的接口前缀
                .excludePathPatterns("/api/login");// 登录接口不拦截
    }
}
 

✅ 效果(和 session 完全一样)

  • Token 设置 2 小时过期
  • 只要你10 分钟内有操作
  • 拦截器自动生成新 Token
  • 前端从响应头拿到新 Token
  • 永远不掉线
这就实现了 JWT 模仿 Session 滑动过期

前端要做什么?(超级简单)

js
 
 
// 请求拦截器 → 带上token
axios.interceptors.request.use(config => {
  config.headers.Authorization = localStorage.getItem('token')
  return config
})

// 响应拦截器 → 如果有新token,自动更新
axios.interceptors.response.use(res => {
  let newToken = res.headers.authorization
  if (newToken) {
    localStorage.setItem('token', newToken)
  }
  return res
})
 

我给你总结这套东西的效果

  • 无状态 JWT
  • 自动续期
  • 和 session 体验完全一样
  • 不用存服务器
  • 分布式 / 多服务器 完美支持
 

1. 登录接口(直接复制到你的 Controller)

java
 
运行
 
 
 
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;

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

    // 登录接口
    @PostMapping("/login")
    public Map<String, Object> login(@RequestBody LoginParam param) {
        Map<String, Object> result = new HashMap<>();

        // 1. 你自己的数据库校验账号密码
        // 这里我直接模拟校验通过
        if ("admin".equals(param.getUsername()) && "123456".equals(param.getPassword())) {

            // 2. 生成 JWT Token
            String token = JwtUtil.createToken("1001", param.getUsername());

            // 3. 返回给前端
            result.put("code", 200);
            result.put("msg", "登录成功");
            result.put("token", token);
            return result;
        }

        // 登录失败
        result.put("code", 401);
        result.put("msg", "账号或密码错误");
        return result;
    }
}
 

2. 登录参数类(LoginParam.java)

java
 
运行
 
 
 
public class LoginParam {
    private String username;
    private String password;

    // getter & setter
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}
 

3. 测试接口返回效果(你会看到)

json
 
 
{
  "code": 200,
  "msg": "登录成功",
  "token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiIxMDAx..."
}
 

4. 整套流程现在完整了 ✅

  1. 前端调用 /api/login → 获取 token
  2. 前端把 token 放在请求头Authorization: token
  3. 拦截器自动鉴权
  4. 快过期时自动续期
  5. 前端自动更新本地 token
和 Session 体验一模一样,但完全是 JWT 无状态!

5. 你现在拥有的完整功能

  • ✅ JWT 生成 / 解析
  • ✅ 自动续期(滑动过期,像 Session)
  • ✅ 登录接口
  • ✅ 鉴权拦截器
  • ✅ 适配 jjwt 0.13.0
  • ✅ 适配 Spring 5.x
  • ✅ 不影响老项目 Session

 

原生 JS、Axios、小程序三种常用场景,直接复制代码即可,同时说明和后端的对应规则。

前置说明

后端约定:请求头 Authorization 存放完整 token,不需要 Bearer 前缀(和上面拦截器匹配)。

一、Axios(最常用,Vue/React/ 普通前端)

1. 全局统一设置(推荐,所有请求自动带 token)

javascript
 
运行
 
 
 
// 1. 先从本地存储取出登录后拿到的token
const token = localStorage.getItem('token');

// 2. 全局请求拦截器,自动附加请求头
axios.interceptors.request.use(function (config) {
    // 存在token就加到请求头
    if (token) {
        config.headers.Authorization = token;
    }
    return config;
}, function (error) {
    return Promise.reject(error);
});

// 3. 响应拦截器:接收后端续期返回的新token,自动更新本地
axios.interceptors.response.use(function (response) {
    const newToken = response.headers.authorization;
    if (newToken) {
        localStorage.setItem('token', newToken);
    }
    return response;
}, function (error) {
    // 401 未授权,跳转到登录页
    if (error.response && error.response.status === 401) {
        localStorage.removeItem('token');
        window.location.href = '/login';
    }
    return Promise.reject(error);
});
 

2. 单个请求单独设置(局部使用)

javascript
 
运行
 
 
 
axios.post('/api/test', {}, {
    headers: {
        Authorization: localStorage.getItem('token')
    }
}).then(res => {
    console.log(res.data);
})
 

二、原生 JavaScript(fetch / XMLHttpRequest)

1. fetch 方式

javascript
 
运行
 
 
 
const token = localStorage.getItem('token');

fetch('/api/test', {
    method: 'GET',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': token  // 放置token
    }
}).then(res => {
    // 接收续期新token
    const newToken = res.headers.get('authorization');
    if (newToken) {
        localStorage.setItem('token', newToken);
    }
    return res.json();
}).then(data => {
    console.log(data);
});
 

2. XMLHttpRequest 方式

javascript
 
运行
 
 
 
const xhr = new XMLHttpRequest();
const token = localStorage.getItem('token');
xhr.open('GET', '/api/test', true);
// 设置请求头
xhr.setRequestHeader('Authorization', token);
xhr.setRequestHeader('Content-Type', 'application/json');

xhr.onload = function() {
    // 更新新token
    const newToken = xhr.getResponseHeader('authorization');
    if (newToken) {
        localStorage.setItem('token', newToken);
    }
    console.log(xhr.responseText);
};
xhr.send();
 

三、微信小程序

javascript
 
运行
 
 
 
const token = wx.getStorageSync('token');

wx.request({
    url: 'https://xxx/api/test',
    header: {
        'Authorization': token, // 携带token
        'content-type': 'application/json'
    },
    success: (res) => {
        // 接收续期token
        const newToken = res.header.authorization;
        if (newToken) {
            wx.setStorageSync('token', newToken);
        }
        console.log(res.data);
    },
    fail: (err) => {
        console.log(err);
    }
})
 

补充关键要点

  1. 存储位置
     
    登录成功后,把接口返回的 token 存入:
    • 网页端:localStorage / sessionStorage
    • 小程序:wx.setStorageSync
  2. 跨域头兼容(后端已处理)
     
    拦截器里加了 Access-Control-Expose-Headers: Authorization,作用是允许前端读取响应头里的新 token,不用额外改代码。
  3. 如果后续要加 Bearer 前缀(拓展)
     
    若后续改成标准格式 Authorization: Bearer 你的token,前端只需要改一行:
    javascript
     
    运行
     
     
     
    config.headers.Authorization = 'Bearer ' + token;
    
     
    同时后端拦截器也要对应截取:
    java
     
    运行
     
     
     
    String token = request.getHeader("Authorization");
    if(token != null && token.startsWith("Bearer ")){
        token = token.substring(7);
    }
posted @ 2026-05-27 15:11  锐洋智能  阅读(13)  评论(0)    收藏  举报