共计 2468 个字符,预计需要花费 7 分钟才能阅读完成。
当浏览器突然弹出 401
昨天测试同事又给我发了个截图——熟悉的红色错误提示框,带着那个刺眼的 {"code":401,"msg":"无 token,请重新登录","data":null}。这已经是本周第三次收到这类反馈了。用户正在填写的长表单突然失效,辛苦输入的数据瞬间消失,这种体验简直比断网还让人崩溃。

认证方式的世代更迭
Session 的烦恼
早些年用 Session-Cookie 方案时,我们得在服务端维护会话存储,每次请求都要查数据库验证 sessionId。当用户量上来后:
- 服务器内存开始告急
- 集群环境下要处理会话同步
- 跨域场景下 Cookie 携带受限
JWT 的曙光
JWT(JSON Web Token)就像一张自带防伪标识的电子门票:
+---------------------+
| Header.Payload.Sign |
+---------------------+
实际解码后的样子:
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
Spring Security 实战手册
核心工具类
public class JwtUtils {
private static final String SECRET_KEY = "你的 256 位密钥";
public static String generateToken(UserDetails user) {return Jwts.builder()
.setSubject(user.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000))
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
.compact();}
public static boolean validateToken(String token) {
try {Jwts.parserBuilder()
.setSigningKey(SECRET_KEY.getBytes())
.build()
.parseClaimsJws(token);
return true;
} catch (Exception e) {
// 具体异常处理逻辑
return false;
}
}
}
认证过滤器
public class JwtFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer")) {response.setContentType("application/json");
response.getWriter().write("{\"code\":401,\"msg\":\" 无 token,请重新登录 \",\"data\":null}");
return;
}
String jwt = token.substring(7);
if (!JwtUtils.validateToken(jwt)) {
// 处理过期 / 篡改等不同异常类型
sendError(response, "Token 已失效");
return;
}
chain.doFilter(request, response);
}
}
安全进阶指南
Token 存储的抉择
- LocalStorage:
- 容易被 XSS 攻击窃取
-
需设置 HttpOnly=false
-
Cookie:
- 要处理 CSRF 防护
- 推荐配置:
ResponseCookie cookie = ResponseCookie.from("token", jwt) .httpOnly(true) .secure(true) .sameSite("Strict") .build();
双 Token 续期策略
- 登录时返回 accessToken(30 分钟) 和 refreshToken(7 天)
- accessToken 过期后,用 refreshToken 获取新 token
- refreshToken 过期则强制重新登录
// 刷新接口示例
@PostMapping("/refresh")
public ResponseEntity<?> refreshToken(@RequestBody RefreshRequest request) {if (redisTemplate.opsForValue().get(request.getRefreshToken()) == null) {throw new InvalidTokenException();
}
// 验证 refreshToken 有效性
// 生成新的 accessToken
}
测试与优化
Postman 测试集
1. 未带 Token 请求受保护接口
- 预期:返回 401 错误
2. 使用过期 Token 请求
- 预期:返回 401 并提示 "Token 已过期"
3. 使用正确 Token 请求
- 预期:正常返回业务数据
性能压测建议
- 使用 JMeter 模拟 1000 并发验证认证模块
- 重点关注:
- Token 校验的响应时间
- 异常情况下的错误率
- 内存泄漏风险
思考与延伸
当我们需要在分布式系统中实现立即失效某个 Token 时(如用户主动登出),可以考虑:
- 将黑名单存入 Redis 并设置 TTL
- 使用 JWT 的 jti(JWT ID)作为唯一标识
- 每次校验时额外检查黑名单
推荐扩展阅读:
– RFC 7519
– OAuth 2.0 与 JWT 的关系
– Spring Security 官方文档
下次当你再看到 401 错误时,希望想到的不只是简单的重新登录,而是背后这套精密的认证机制在守护着系统安全。
正文完
发表至: 技术开发
近三天内
