《JavaWeb登录校验全链路实战:从Session到JWT,Filter与Interceptor深度解析》

6 阅读7分钟

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 在内层

就像你进公司:

  1. 先过大门卫(Filter)
  2. 再见部门秘书(Interceptor)
  3. 办完事,先跟秘书告别,再跟门卫说再见

🔄 完整执行流程

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 正序,postHandleafterCompletion 倒序

五、Filter vs Interceptor:核心区别(收藏级表格)

对比维度FilterInterceptor
所属规范Servlet 规范Spring MVC 组件
依赖 Spring❌ 否✅ 是
拦截范围所有请求(含静态资源)仅 Controller 请求
执行时机DispatcherServlet 之前DispatcherServlet 之后
注入 Bean不能直接注入(需特殊处理)可直接注入 Service 等
异常捕获可捕获整个请求链异常仅能捕获 Controller 及自身异常
典型用途编码、CORS、全局日志、安全头权限校验、耗时统计、上下文注入

六、实战选型避坑指南(看完少走 3 年弯路)

  1. 🚫 不要用 Filter 注入 Spring Bean
    Filter 不在 Spring 容器中,直接 @Autowired 会 NPE。如需注入,可用 WebApplicationContextUtils 获取上下文。
  2. 🚫 不要用 Interceptor 拦截静态资源
    默认不拦截,强行配置会浪费性能。
  3. ✅ 分布式系统推荐:JWT + Interceptor
    JWT 无状态适配微服务,Interceptor 可注入权限服务做细粒度控制。
  4. ✅ 单体应用推荐:Session + Filter
    简单直接,Filter 拦所有请求,无需考虑路径遗漏。
  5. ✅ 放行路径要一致
    如果 Filter 放行了 /login,Interceptor 也要放行,否则会出现“明明登录了还被拦”的诡异问题。

七、总结

登录校验的本质是 “识别合法用户” ,而 Session 与 JWT 是两种实现路径,Filter 与 Interceptor 是两种执行工具。

记住这三个核心原则,你就能游刃有余:

  1. 会话选型:分布式用 JWT,单体用 Session;
  2. 工具选型:需拦静态资源用 Filter,需注入 Bean 用 Interceptor;
  3. 执行顺序:Filter 先进后出,Interceptor 中间按规则执行。

最后留个小问题:JWT 令牌过期了怎么办?你是用刷新令牌(Refresh Token)机制,还是让用户重新登录?欢迎在评论区交流你的方案!


如果觉得本文对你有帮助,欢迎点赞 ❤️、收藏 📌、转发 🔄!
关注我,获取更多 JavaWeb & Spring Boot 实战干货!


版权声明:本文首发于稀土掘金,转载请注明出处。