Session 方式在分布式环境下不好用,JWT(JSON Web Token)是目前主流的无状态认证方案。服务端不需要存 Session,Token 中包含了用户信息。
一、JWT 结构
header.payload.signature
header: 加密算法
payload: 用户数据(不要放密码)
signature: 签名,防止篡改
二、JWT 工具类
@Component
public class JwtUtil {
@Value("${jwt.secret:mySecretKey}")
private String secret;
@Value("${jwt.expire:86400000}")
private long expire; // 默认24小时
public String generateToken(Long userId, String username) {
Date now = new Date();
return Jwts.builder()
.setSubject(userId.toString())
.claim("username", username)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + expire))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
public boolean validateToken(String token) {
try {
parseToken(token);
return true;
} catch (Exception e) {
return false;
}
}
}
三、登录接口
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public ResultVO<Map<String, Object>> login(
@RequestParam String username,
@RequestParam String password) {
// 校验用户名密码
if (!"admin".equals(username) || !"123456".equals(password)) {
return ResultVO.error(401, "用户名或密码错误");
}
// 生成 Token
String accessToken = jwtUtil.generateToken(1001L, username);
String refreshToken = jwtUtil.generateRefreshToken(1001L);
Map<String, Object> result = new HashMap<>();
result.put("accessToken", accessToken);
result.put("refreshToken", refreshToken);
result.put("expiresIn", 86400);
return ResultVO.success(result);
}
}
四、Token 刷新
@PostMapping("/refresh")
public ResultVO<Map<String, Object>> refresh(
@RequestParam String refreshToken) {
if (!jwtUtil.validateToken(refreshToken)) {
return ResultVO.error(401, "RefreshToken 无效");
}
Claims claims = jwtUtil.parseToken(refreshToken);
Long userId = Long.parseLong(claims.getSubject());
String username = claims.get("username", String.class);
String newAccessToken = jwtUtil.generateToken(userId, username);
String newRefreshToken = jwtUtil.generateRefreshToken(userId);
return ResultVO.success(Map.of(
"accessToken", newAccessToken,
"refreshToken", newRefreshToken
));
}
五、拦截器校验
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
throw new BusinessException(401, "未登录");
}
token = token.substring(7);
if (!jwtUtil.validateToken(token)) {
throw new BusinessException(401, "Token 已过期");
}
// 将用户信息存入请求上下文
Claims claims = jwtUtil.parseToken(token);
request.setAttribute("userId", claims.getSubject());
request.setAttribute("username", claims.get("username"));
return true;
}
}
六、前端调用
// 登录
const resp = await fetch('/auth/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username: 'admin', password: '123456'})
});
const { accessToken, refreshToken } = await resp.json();
// 后续请求携带 Token
fetch('/api/user', {
headers: {'Authorization': 'Bearer ' + accessToken}
});
// Token 过期时用 refreshToken 刷新
async function refreshAccessToken() {
const resp = await fetch('/auth/refresh', {
method: 'POST',
body: new URLSearchParams({refreshToken})
});
const data = await resp.json();
accessToken = data.accessToken;
}
七、安全注意事项
// 1. JWT 中不要放敏感信息
Claims claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
String password = claims.get("password"); // ❌ 错误!
// 2. 密钥不要写死,用配置中心或环境变量
@Value("${jwt.secret}")
private String secret;
// 3. accessToken 有效期短(15-30分钟)
// refreshToken 有效期长(7天),专门用来刷新 accessToken
总结
Session 认证:服务端存 Session,分布式下需要共享 Session
JWT 认证: 无状态,Token 携带用户信息,适合分布式
JWT 缺点:签发后无法主动失效(只能等过期)
解决方案:accessToken 短时效 + refreshToken 刷新机制
💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。

22万+

被折叠的 条评论
为什么被折叠?



