인셔셔RSS 관심 있는 블로그, 뉴스, 기술 정보를 효율적으로 추적하고 읽으세요
원문 읽기 InertiaRSS에서 열기

추천 피드

小众软件
小众软件
博客园 - 叶小钗
有赞技术团队
有赞技术团队
大猫的无限游戏
大猫的无限游戏
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
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 的主要区别
Spring 5.x + 구전 프로젝트 JWT 인터셉터 + 자동 갱신
锐洋智能 · 2026-05-27 · via 博客园 - 锐洋智能

최종 완전한 코드: 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 인터셉터(자동 갱신 + 자동 새 토큰 반환)

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");// 登录接口不拦截
    }
}

✅ 결과(세션과 완전히 동일)

  • 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
  • 자동 연장
  • 과 세션 경험은 완전히 동일합니다
  • 서버에 저장할 필요가 없습니다
  • 분산 / 다수의 서버를 완벽하게 지원합니다

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 호출 → 토큰을 얻음
  2. 프론트엔드가 토큰을 요청 헤더에 추가Authorization: token
  3. 인터셉터가 자동으로 인증
  4. 빠르게 만료될 때 자동으로 갱신
  5. 프론트엔드가 자동으로 로컬 토큰

을 업데이트


세션과 완전히 동일한 경험을 제공하지만, 완전히 JWT 무상태입니다!

  • 5. 현재 보유한 모든 기능
  • ✅ JWT 생성 / 해석
  • ✅ 자동 갱신(스와이프 만료, 세션과 같음)
  • ✅ 로그인 인터페이스__JHSNS_SEG_fd4b0228_55__✅ 인증 인터셉터
  • ✅ jjwt 0.13.0에 맞게 조정됨
  • ✅ Spring 5.x에 맞게 조정됨
  • ✅ 기존 프로젝트 세션에 영향을 주지 않음

원시 JS, Axios, 소프트웨어세 가지 일반적인 시나리오에 직접 코드를 복사하여 사용 가능하며, 뒷단과의 대응 규칙을 설명합니다.

선전 설명

뒷단 약속: 요청 헤더 Authorization에 완전한 토큰을 저장하고, Bearer 접두사가 필요 없습니다(위拦截기와 일치합니다)。


일부, Axios(가장 일반적, Vue/React/일반 프론트엔드)

1. 전역 통합 설정(권장, 모든 요청에 자동으로 토큰 포함)

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

2. 원시 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();

3. WeChat Mini Program

자바스크립트

실행

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, 역할은프론트엔드가 응답 헤더에 있는 새 토큰을 읽을 수 있습니다, 별도로 코드를 수정하지 마세요.

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