












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");
}
}
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;
}
}
}
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");// 登錄接口不攔截
}
}
這就實現了 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
})
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;
}
}
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..."
}
Authorization: token和 Session 體驗一模一樣,但完全是 JWT 無狀態!
分原生 JS、Axios、小程序三種常用場景,直接複製代碼即可,同時說明和後端的對應規則。
後端約定:請求頭 Authorization 存放完整 token,不需要 Bearer 前綴(和上面攔截器匹配)。
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
運行
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);
});
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);
}
})
存儲位置
登錄成功後,把接口返回的 token 存入:
localStorage / sessionStoragewx.setStorageSync跨域頭兼容(後端已處理)
攔截器里加了 Access-Control-Expose-Headers: Authorization,作用是允許前端讀取響應頭裡的新 token,不用額外改代碼。
如果後續要加 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);
}
此內容由慣性聚合(RSS閱讀器)自動聚合整理,僅供閱讀參考。 原文來自 — 版權歸原作者所有。