JavaWeb登录校验实战宝典:从 Session 到 JWT,Filter 与 Interceptor 全解析
标签:Java / Spring Boot / Web 安全 / 后端开发
在 JavaWeb 开发的江湖里,登录校验堪称守护系统安全的“御前侍卫”——既要拦住非法闯入者,又不能耽误合法用户办事。你是不是也曾在这些问题上栽过跟头:
- Session 存用户信息,在分布式环境下总出岔子?
- JWT 签名验证时频频报 “签名不匹配”?
- 过滤器和拦截器同时配置后,执行顺序乱得像一团麻?
别慌!本文将用 “原理 + 代码 + 避坑指南” 的三重 buff,带你从会话技术选型到校验工具实战,再到 Filter 与 Interceptor 的“相爱相杀”,彻底把登录校验这块硬骨头啃下来!
一、登录校验的基石:会话技术二选一
登录校验的核心是 “记住用户身份” ,而会话技术就是实现这一目标的两大法宝:传统的 Session 和 现代的 JWT。它们就像武侠小说里的“重剑”和“轻剑”,各有妙用。
1. 重剑无锋:Session 会话(服务器端存储)
Session 的逻辑很简单:用户登录成功后,服务器给 TA 办一张“身份证”(Session 对象),再把身份证号(SessionID)通过 Cookie 塞给客户端。后续客户端每次请求都带着身份证号,服务器一查就知道是谁了。
✅ 代码实战(Spring Boot 环境)
@PostMapping("/login")
public Result login(@RequestBody User user, HttpSession session) {
if ("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())) {
session.setAttribute("loginUser", user);
session.setMaxInactiveInterval(30 * 60); // 30分钟过期
return Result.success("登录成功,欢迎回来!");
}
return Result.error("用户名或密码错啦,再试试~");
}
@GetMapping("/user/info")
public Result getUserInfo(HttpSession session) {
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser == null) {
return Result.error("请先登录再操作哦~");
}
return Result.success("用户信息", loginUser);
}
⚖️ 优缺点盘点
| 优点 | 缺点 |
|---|---|
| 上手简单,无需客户端额外处理 | 分布式环境下 Session 不共享 |
| 用户信息存在服务器,安全性高 | 依赖 Cookie,移动端/跨域场景受限 |
| 自动管理生命周期 | 集群部署需引入 Redis 等方案 |
适用场景:单体应用、内部系统、对安全性要求高且无跨域需求。
2. 轻剑快马:JWT 令牌(客户端存储)
JWT(JSON Web Token)是一串加密的 JSON 字符串,相当于给用户发一张“电子身份证”,里面装着用户信息和有效期。服务器无需存储任何东西——收到令牌后只要验证签名没问题,就认这个用户。
它由三部分组成:Header.Payload.Signature,形如 xxx.yyy.zzz。
🔧 第一步:引入依赖(避坑 JAXB 缺失)
很多同学用 JWT 时会碰到 NoClassDefFoundError: javax/xml/bind/DatatypeConverter,这是 Java 9+ 移除了 JAXB 模块导致的。添加以下依赖即可解决:
<!-- JJWT 核心依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- 解决 JAXB 缺失问题 -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
🛠️ 第二步:写个 JWT 工具类(生成 + 解析)
public class JwtUtils {
private static final String SECRET_KEY = "your_32_byte_secret_key_here"; // 生产环境务必复杂且保密
private static final long EXPIRATION = 2 * 60 * 60 * 1000; // 2小时
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public static Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody();
}
}
⚠️ 注意:Payload 是 Base64 编码,不是加密!切勿存放敏感信息(如密码、手机号)。
✅ 登录接口生成令牌
@PostMapping("/login")
public Result login(@RequestBody User user) {
if ("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())) {
String token = JwtUtils.generateToken(user.getUsername());
return Result.success("登录成功", Map.of("token", token));
}
return Result.error("用户名或密码错误");
}
⚖️ 优缺点盘点
| 优点 | 缺点 |
|---|---|
| 无状态,天然支持分布式 | 令牌一旦签发无法主动失效(除非引入黑名单) |
| 支持跨域、前后端分离 | 密钥泄露 = 系统沦陷 |
| 减少数据库查询(可携带非敏感信息) | 体积比 SessionID 大,增加网络开销 |
适用场景:微服务架构、前后端分离、跨域应用、移动端 API。
📌 选型小技巧
- 单体应用 + 无跨域 → 选 Session
- 分布式 + 跨域 + 前后端分离 → 选 JWT
二、登录校验的实现:过滤器(Filter)实战
Filter 是 Servlet 规范中的“大门卫”——所有进入容器的请求(包括静态资源)都要经过它的检查。
✅ 实现步骤
第一步:自定义 Filter
@Component
@WebFilter(urlPatterns = "/*")
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String uri = request.getRequestURI();
if ("/login".equals(uri)) {
chain.doFilter(request, response);
return;
}
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(401);
response.getWriter().write("未登录:请提供有效令牌");
return;
}
try {
String jwt = token.substring(7); // 去掉 "Bearer "
JwtUtils.parseToken(jwt);
chain.doFilter(request, response);
} catch (Exception e) {
response.setStatus(401);
response.getWriter().write("令牌无效或已过期");
}
}
}
💡 建议:生产环境使用
Authorization: Bearer <token>标准格式。
第二步:启用 Filter 扫描
@SpringBootApplication
@ServletComponentScan // 必须加!否则 @WebFilter 不生效
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
🔍 Filter 核心特性
- 执行时机:在
DispatcherServlet之前 - 拦截范围:所有请求(含
.js,.css,.png) - 依赖:Servlet 规范,不依赖 Spring 容器
- 放行方式:调用
filterChain.doFilter(...)
三、登录校验的进阶:拦截器(Interceptor)实战
Interceptor 是 Spring MVC 的“专属秘书”——只关注 Controller 请求,还能在方法执行前后插入逻辑。
✅ 实现步骤
第一步:自定义 Interceptor
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (handler instanceof HandlerMethod) { // 确保是 Controller 方法
String uri = request.getRequestURI();
if ("/login".equals(uri)) return true;
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(401);
return false;
}
try {
JwtUtils.parseToken(token.substring(7));
return true;
} catch (Exception e) {
response.setStatus(401);
return false;
}
}
return true; // 静态资源等非 Controller 请求直接放行
}
}
第二步:注册 Interceptor
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login", "/static/**");
}
}
🔍 Interceptor 核心特性
-
执行时机:在
DispatcherServlet之后,Controller 之前 -
拦截范围:仅 Controller 请求
-
依赖:依赖 Spring 容器,可注入 Service、Repository
-
三个钩子方法:
preHandle:Controller 前postHandle:Controller 后、视图渲染前afterCompletion:请求完全结束
四、灵魂拷问:Filter 和 Interceptor 同时存在,谁先执行?
记住一句话:Filter 在外层,Interceptor 在内层。
就像你进公司:
- 先过大门卫(Filter)
- 再见部门秘书(Interceptor)
- 办完事,先跟秘书告别,再跟门卫说再见
🔄 完整执行流程
HTTP Request
↓
[Filter1.pre → Filter2.pre → ...]
↓
DispatcherServlet
↓
[Interceptor1.preHandle → Interceptor2.preHandle → ...]
↓
Controller Method
↓
[Interceptor2.postHandle → Interceptor1.postHandle]
↓
View Rendering (if any)
↓
[Interceptor2.afterCompletion → Interceptor1.afterCompletion]
↓
[Filter2.post → Filter1.post]
↓
HTTP Response
✅ 规律总结:
- Filter:按注册顺序 正序进入,倒序退出
- Interceptor:
preHandle正序,postHandle和afterCompletion倒序
五、Filter vs Interceptor:核心区别(收藏级表格)
| 对比维度 | Filter | Interceptor |
|---|---|---|
| 所属规范 | Servlet 规范 | Spring MVC 组件 |
| 依赖 Spring | ❌ 否 | ✅ 是 |
| 拦截范围 | 所有请求(含静态资源) | 仅 Controller 请求 |
| 执行时机 | DispatcherServlet 之前 | DispatcherServlet 之后 |
| 注入 Bean | 不能直接注入(需特殊处理) | 可直接注入 Service 等 |
| 异常捕获 | 可捕获整个请求链异常 | 仅能捕获 Controller 及自身异常 |
| 典型用途 | 编码、CORS、全局日志、安全头 | 权限校验、耗时统计、上下文注入 |
六、实战选型避坑指南(看完少走 3 年弯路)
- 🚫 不要用 Filter 注入 Spring Bean
Filter 不在 Spring 容器中,直接@Autowired会 NPE。如需注入,可用WebApplicationContextUtils获取上下文。 - 🚫 不要用 Interceptor 拦截静态资源
默认不拦截,强行配置会浪费性能。 - ✅ 分布式系统推荐:JWT + Interceptor
JWT 无状态适配微服务,Interceptor 可注入权限服务做细粒度控制。 - ✅ 单体应用推荐:Session + Filter
简单直接,Filter 拦所有请求,无需考虑路径遗漏。 - ✅ 放行路径要一致
如果 Filter 放行了/login,Interceptor 也要放行,否则会出现“明明登录了还被拦”的诡异问题。
七、总结
登录校验的本质是 “识别合法用户” ,而 Session 与 JWT 是两种实现路径,Filter 与 Interceptor 是两种执行工具。
记住这三个核心原则,你就能游刃有余:
- 会话选型:分布式用 JWT,单体用 Session;
- 工具选型:需拦静态资源用 Filter,需注入 Bean 用 Interceptor;
- 执行顺序:Filter 先进后出,Interceptor 中间按规则执行。
最后留个小问题:JWT 令牌过期了怎么办?你是用刷新令牌(Refresh Token)机制,还是让用户重新登录?欢迎在评论区交流你的方案!
如果觉得本文对你有帮助,欢迎点赞 ❤️、收藏 📌、转发 🔄!
关注我,获取更多 JavaWeb & Spring Boot 实战干货!
版权声明:本文首发于稀土掘金,转载请注明出处。