












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");// 登录接口不拦截
}
}
、永遠に切断されないように、これで __JHSNS_SEG_a2742d6e_23__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状態で状態がありません!
分ネイティブ JavaScript、Axios、小程序の3つの一般的なシナリオで、コードをコピーして直接使用でき、後端への対応ルールについても説明します。
後端の規約:リクエストヘッダー Authorization に完全なトークンを格納し、 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を追加し、その役割はフロントエンドがレスポンスヘッダー内の新しいトークンを読み取ることができるように許可することです。追加のコード変更は不要です。
以降で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リーダー)によって自動集約されています。参考としてご覧ください。 原文出典 — 著作権は原著者に帰属します。