慣性聚合 高效追讀感興趣之博客、新聞、科技資訊
閱原文 以慣性聚合開啟

推薦訂閱源

小众软件
小众软件
博客园 - 叶小钗
有赞技术团队
有赞技术团队
大猫的无限游戏
大猫的无限游戏
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LangChain Blog
Hugging Face - Blog
Hugging Face - Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
aimingoo的专栏
aimingoo的专栏
Blog — PlanetScale
Blog — PlanetScale
爱范儿
爱范儿
T
Tailwind CSS Blog
Jina AI
Jina AI
量子位
Stack Overflow Blog
Stack Overflow Blog
人人都是产品经理
人人都是产品经理
J
Java Code Geeks
V
Visual Studio Blog
月光博客
月光博客

博客园 - 锐洋智能

Eclipse IDE for Enterprise Find/Replace 窗口可以"停驻" 安装 SDelete 方式 SDelete 的核心作用,不是 “删文件”,而是 “把你已经删掉的文件,彻底从磁盘上抹干净”,同时帮你把虚拟机里的 “空闲空间” 变成连续的、可被回收的状态。 windows 10 启动就运行了一个批处理文件 在什么地方修改?启动项中? 让 Spring Framework7.0.7 支持 velocity Java 9+ 开启了模块化安全限制,不允许 Ignite 直接访问底层内存地址,导致 Ignite 启动失败 接口鉴权:Session/Cookie 与 JWT 的核心区别 下是针对 RedisSessionManager 的 Tomcat context.xml 配置示例,覆盖基础单机 Redis、带密码 / 指定库、Redis 哨兵集群、自定义序列化 / 持久化策略 等常见场景 Redis-8.6.3-Windows-x64-cygwin 与 Redis-8.6.3-Windows-x64-msys2 有什么不一样? commons-fileupload2 M4 升级 M5 报错解决方案 阿里云的网络安全策略 ip 地址详细说明 Paralithic、 QLExpress、AviatorScript、exp4j 性能对比一下 java 序列化影响(重要!) paralithic 与 Aviator 表达式那一个更快 券商接受委托的完整时间线 jQuery 4.0 移除了许多已废弃的方法和特性 xheditor插件无限递归错误解决方法 jedis-7.1.0.jar 升级至 jedis-7.2.0.jar 就提示:The type JedisPoolConfig is deprecated The type JedisPooled is deprecated Spring 从 5.x 到 6.x 和 7.x 区别 js 原生 剪切板 复制功能 安装 Microsoft Visual C++ 运行时 Java与Python进程通信优化方案 Autocomplete | jQuery UI 同一面页定义不同的 .ui-autocomplete Quartz的misfire处理策略设置不当导致Job在应用启动时立即执行。 mysql-connector-j-9.5.0.jar BUG 推荐几款好用的CSS在线压缩工具 CSS 变量 定义及引用 TLS virtual host [_default_], certificate type [RSA] configured from keystore [d:/reyo/jks/localhost.jks] using alias [tomcat] with trust store [null] okhttp5.x 需要在 请求级别 控制是否使用重试拦截器,而不是在客户端级别 Apache Commons FileUpload 1.x 与 2.x 的主要区别
春五加旧案之JWT拦截器,自动续期之术
锐洋智能 · 2026-05-27 · via 博客园 - 锐洋智能

终得全码:JWT拦截器+自动续期

1. 先予之续期之术也(增入尔之JwtUtil中)

Java(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");
    }
}

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;
        }
    }
}

三、注册拦截器(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之设二时过矣
  • 只要你十分钟内可操作
  • 拦截器自生新 Token
  • 前端自响应首获新令符
  • 永不断线

是故得之JWT 模拟 Session 滑动过期


夫前端之务何为?(至简也)

JavaScript

// 请求拦截器 → 带上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
})

吾为汝述此套之效验

  • 无状态 JSON Web Token
  • 自動續期
  • 與 session 之體驗無異
  • 無需存於伺服器
  • 分佈式/多伺服器完美支援

一、登錄接口(直接複製至汝之 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;
    }
}

二、登錄參數類(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;
    }
}

三、測試接口返回之效(汝將見之)

json

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

四、整套流程今已完整 ✅

  1. 前端呼 /api/login得令牌
  2. 前端将 token 置于请求数之上Authorization: token
  3. 拦截器自动认证
  4. 期近而自续
  5. 前端自更新本地令符

与 Session 之体验无别,然纯 JWT 无状态也。


五、尔今所具之全功

  • ✅ JWT 生成 / 解析
  • ✅ 自动续约(滑过即逝,类会话)
  • ✅ 登陆之接口
  • 鉴权拦截器
  • ✅ 适 JJWT 0.13.0
  • ✅ 适 Spring 5.x
  • ✅ 不损旧制 Session

原 JS、Axios、微程 三境常用,直抄代码可,兼述与后端对应之则。

前置说明

后端约定:请头 Authorization 存全 token, 无需 Bearer 前缀 (与上拦截器相匹)


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

一、全域统设(首推,凡请皆自带 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);
});

二、独请独设(局部用)

javascript

运行

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

二、本源 JavaScript(fetch / XMLHttpRequest)

一、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);
});

二、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();

三、WeChat Mini Program

JavaScript(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(Java)

    运行

    String token = request.getHeader("Authorization");
    if(token != null && token.startsWith("Bearer ")){
        token = token.substring(7);
    }