上篇登录跑通,拿到了 token。这篇解决"token 发出去之后怎么用":拦截器统一验签,业务代码零感知地拿到当前用户。
为什么不用 Spring Security
我的场景:两种角色(USER/DRIVER/ADMIN)、几十个接口、无 OAuth 无 SSO。Spring Security 的过滤器链、SecurityContext、UserDetailsService 那一套,学习成本远超收益。手写一个 JWT 工具 + 一个 MVC 拦截器,总代码不到 200 行 ,每一行都看得懂、改得动。小项目的安全感来自简单,不是来自重量级框架。

三件套:JwtUtil、AuthContext、拦截器
1. JwtUtil:手写 HS256
JWT 结构是 header.payload.signature,HS256 的签名就是 HMAC-SHA256(header + "." + payload, secret)。不引第三方库也能写:
java
public static String create(Long userId, String role) {
String header = base64("{\"alg\":\"HS256\",\"typ\":\"JWT\"}");
String payload = base64("{\"uid\":" + userId + ",\"role\":\"" + role
+ "\",\"exp\":" + (System.currentTimeMillis()/1000 + 7*86400) + "}");
String sign = hmacSha256(header + "." + payload, SECRET);
return header + "." + payload + "." + sign;
}
解析时先验签再取 uid/role,过期抛异常。就这么多,没有魔法。
2. AuthContext:ThreadLocal 存当前用户
java
public class AuthContext {
private static final ThreadLocal<Long> UID = new ThreadLocal<>();
public static void set(Long uid) { UID.set(uid); }
public static Long uid() { return UID.get(); }
public static void clear() { UID.remove(); } // 关键!
}
3. 拦截器:验签 + 放行名单
java
public class AuthInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object h) {
String token = req.getHeader("Authorization"); // "Bearer eyJ..."
Long uid = JwtUtil.parseUserId(token.replace("Bearer ", ""));
if (uid == null) { resp.setStatus(401); return false; }
AuthContext.set(uid);
return true;
}
public void afterCompletion(...) { AuthContext.clear(); }
}
注册时排除公开接口(/api/auth/**、/api/vehicle-types、管理端登录等)。
之后所有 Controller 写起来非常干净:
java
@GetMapping("/api/me")
public Result me() {
return Result.ok(userService.info(AuthContext.uid())); // 当前用户直接拿
}
三个必踩的坑,提前避开
- ThreadLocal 必须 remove :Tomcat 线程是复用的,不清理会串用户------这是隐蔽性最高的线上事故来源。放
afterCompletion里最保险 - 放行名单别用通配符放开整个 /api:宁可逐个加白名单
- 过期策略从简:7 天过期 + 过期重登。token 自动续期(refresh token)第一版不需要,别给自己加戏
本篇成果
全站鉴权闭环:前端请求头自动带 token → 拦截器验签 → 业务代码 AuthContext.uid() 直取当前用户。下一篇写整个系统的心脏:订单状态机与下单支付。