javaweb-Day09-Jwt令牌&Filter&Interceptor

TJCcc 发布于 5 天前 8 次阅读


一、会话跟踪技术概述

HTTP 是无状态协议,每次请求都是独立的,服务器默认无法判断两次请求是否来自同一个用户。会话跟踪技术用于在多个请求之间维持用户状态。常见方案包括:

  • Cookie
  • Session
  • JWT(JSON Web Token)

下面先简单介绍 Cookie 和 Session,再重点介绍 JWT,以及 Java Web 中常用的过滤器 Filter 和拦截器 Interceptor。


二、Cookie 与 Session

2.1 Cookie

定义
Cookie 是服务器通过 Set-Cookie 响应头写入浏览器、由浏览器保存的小型文本数据。浏览器后续请求同一域名时,会自动通过 Cookie 请求头携带。

关键属性

属性说明
Name / ValueCookie 的键值对
Domain作用域,指定哪些域名可以访问
Path指定哪些路径会携带该 Cookie
Expires / Max-Age过期时间,不设置则为会话 Cookie
HttpOnly禁止 JavaScript 读取,降低 XSS 风险
Secure仅允许 HTTPS 传输
SameSite限制跨站请求携带,用于 CSRF 防护

工作流程

  1. 用户首次访问,服务器通过 Set-Cookie: userId=abc; HttpOnly; Path=/ 写入 Cookie。
  2. 浏览器保存 Cookie。
  3. 后续请求自动携带 Cookie: userId=abc
  4. 服务器读取并识别用户。

优缺点

  • 优点:数据保存在客户端,减轻服务器存储压力。
  • 缺点:大小约 4KB;可被用户禁用;默认明文存储,不适合保存敏感数据。

2.2 Session

定义
Session 是保存在服务器端的用户会话数据。每个会话有一个唯一标识 SessionId,通常通过 Cookie 传给客户端。

工作流程

  1. 用户登录成功,服务器创建 Session,保存用户信息,例如 session.setAttribute("user", user)
  2. 服务器返回 Set-Cookie: JSESSIONID=xxxx; HttpOnly
  3. 浏览器后续请求自动携带 JSESSIONID
  4. 服务器根据 JSESSIONID 找到对应 Session,读取用户信息。

存储方式
默认可存内存,也可持久化到 Redis、数据库等。生产环境通常使用 Redis 实现 Session 共享。

优缺点

  • 优点:数据保存在服务端,安全性相对较高;可存储较大对象;服务端可主动销毁。
  • 缺点:占用服务端内存/存储资源;分布式环境下需要额外做 Session 共享;依赖 Cookie 传递 SessionId。

2.3 Cookie 与 Session 对比

对比维度CookieSession
存储位置客户端浏览器服务端
安全性较低,数据可被篡改、查看较高,数据在服务端
容量约 4KB理论上无限制
性能降低服务端存储压力占用服务端内存或外部存储
扩展性好,服务端无状态差,分布式需要 Session 共享
主动失效可设置过期时间,但服务端不可控服务端可随时销毁 Session
应用场景记住登录、偏好设置传统 Web 登录状态、购物车

三、JWT 令牌

3.1 定义

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传递 JSON 格式的声明。JWT 本身包含用户身份信息,并通过数字签名保证内容不可篡改。

JWT 是无状态的,服务端不需要保存会话信息,非常适合分布式、前后端分离、移动端和微服务场景。


3.2 JWT 组成结构

JWT 由三部分组成:

Header.Payload.Signature

使用 . 分隔,例如:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMDAxIn0.abcxyz

1. Header

描述签名算法和令牌类型,JSON 格式:

{
  "alg": "HS256",
  "typ": "JWT"
}

经过 Base64Url 编码后作为第一部分。

2. Payload

存放声明(Claims),包含用户信息及令牌信息:

{
  "sub": "1001",
  "name": "Zhang San",
  "role": "ADMIN",
  "iat": 1710000000,
  "exp": 1710003600
}

常见声明:

  • iss:签发者
  • sub:主题,通常为用户 ID
  • exp:过期时间
  • iat:签发时间
  • nbf:生效时间
  • jti:令牌唯一 ID
  • 自定义声明:如 roleuserId

注意:Base64Url 编码不是加密,任何人都可以解码 Payload,因此 不能存放密码、银行卡号等敏感信息

3. Signature

签名用于验证 JWT 是否被篡改。

使用 HMAC 算法时,签名公式:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

服务端验签时,使用相同密钥重新计算签名并比对,从而确保内容未被修改。

常用签名算法:

  • HS256:HMAC + SHA256,对称加密,共享密钥。
  • RS256:RSA + SHA256,非对称加密,私钥签名、公钥验签。

3.3 JWT 认证流程

用户登录 -> 服务端校验账号密码
       -> 服务端生成 JWT
       -> 返回 JWT 给客户端
       -> 客户端保存 JWT(localStorage / Cookie)
       -> 后续请求携带:Authorization: Bearer <token>
       -> 服务端解析并验签
       -> 获取用户信息,处理请求

3.4 JWT 优缺点

优点

  • 无状态:服务端不保存会话,水平扩展容易。
  • 跨语言、跨域友好:适合前后端分离、移动端、微服务。
  • 自包含:用户信息包含在 Token 中,减少数据库查询。

缺点

  • 无法主动失效:一旦签发,在过期前无法直接作废,需要引入黑名单或短过期时间。
  • 体积较大:相比 SessionId 更长,占用带宽。
  • Payload 可解码:不能存放敏感信息。
  • 泄露风险高:泄露后直到过期前都可能被冒用,需配合 HTTPS、短过期时间、刷新令牌等。

3.5 JWT 与 Session 对比

对比维度JWTSession
状态无状态有状态
存储位置客户端服务端
扩展性好,适合分布式需要共享存储
主动失效容易
安全性需妥善保管,泄露风险高服务端可控制
性能服务端验签有 CPU 开销,带宽占用大服务端查询有 IO/内存开销
适用场景API、微服务、前后端分离传统 Web 应用

3.6 Java 使用示例

以 jjwt 库为例(示例 API 为 0.11.x 版本):

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;

public class JwtUtil {

    private static final String SECRET = "my-secret-key-my-secret-key-my-secret-key";
    private static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));

    /**
     * 生成 JWT
     */
    public static String generateToken(String userId, String role) {
        long now = System.currentTimeMillis();
        return Jwts.builder()
                .setSubject(userId)
                .claim("role", role)
                .setIssuedAt(new Date(now))
                .setExpiration(new Date(now + 3600_000)) // 1小时
                .signWith(KEY, SignatureAlgorithm.HS256)
                .compact();
    }

    /**
     * 解析并验签 JWT
     */
    public static Claims parseToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(KEY)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
}

实际项目中应注意:

  • 密钥通过配置文件或环境变量注入,不要硬编码。
  • 校验时指定允许的签名算法,防止算法混淆攻击。
  • 使用 HTTPS 传输。
  • Token 可存放在 HttpOnly Cookie 或客户端安全存储中,需权衡 XSS 与 CSRF 风险。

四、过滤器 Filter

4.1 定义

Filter 是 Servlet 规范中的组件,用于在请求到达 Servlet/Controller 之前、以及响应返回客户端之前,对请求和响应进行拦截处理。

Filter 是 Java Web 应用中最底层的请求处理组件之一,先于 Spring MVC 的 DispatcherServlet 执行。

传统javaweb底层三大组件:Servlet,Filter,Listener


4.2 生命周期

Filter 接口包含三个方法:

public interface Filter {
    void init(FilterConfig filterConfig);
    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain);
    void destroy();
}
  • init():Filter 初始化时调用,只执行一次。
  • doFilter():每次请求都会调用,必须调用 chain.doFilter() 放行,否则请求中断。
  • destroy():应用关闭时执行,用于资源清理。

4.3 过滤器链

多个 Filter 可以组成链式调用:

请求 -> Filter1 -> Filter2 -> Servlet/Controller
响应 <- Filter1 <- Filter2 <- Servlet/Controller

执行顺序可通过 @Order 注解或实现 Ordered 接口控制,值越小越先执行。


4.4 应用场景

  • 字符编码处理,如 CharacterEncodingFilter
  • CORS 跨域配置
  • 请求日志、访问量统计
  • 统一鉴权(粗粒度)
  • XSS 过滤、敏感词过滤
  • 响应压缩、缓存控制
  • 请求包装,修改参数或头信息

4.5 代码示例

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
@Order(1)
public class AuthFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String token = httpRequest.getHeader("Authorization");

        if (token == null || !token.startsWith("Bearer ")) {
            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "未登录");
            return;
        }

        // 可在此校验 JWT
        chain.doFilter(request, response);
    }
}

法二:也可通过 Spring Boot 的 FilterRegistrationBean 注册,精确控制拦截路径:

@Bean
public FilterRegistrationBean<AuthFilter> authFilterRegistration(AuthFilter filter) {
    FilterRegistrationBean<AuthFilter> registration = new FilterRegistrationBean<>();
    registration.setFilter(filter);
    registration.addUrlPatterns("/api/*");
    registration.setOrder(1);
    return registration;
}

4.6 特点

  • 基于 Servlet 容器,先于 Spring MVC 执行。
  • 可以拦截所有请求,包括静态资源、错误页。
  • 可以包装请求和响应,例如通过 HttpServletRequestWrapper 修改参数、头信息。
  • 不依赖 Spring 容器,但在 Spring Boot 中通常由容器管理,可注入 Bean。

五、拦截器 Interceptor

5.1 定义

Interceptor 是 Spring MVC 框架提供的组件,用于在处理器(Controller 方法)执行前后进行拦截处理。它比 Filter 更贴近业务层,能访问 Spring 容器和 Handler 方法元数据。


5.2 核心方法

public interface HandlerInterceptor {

    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                    ModelAndView modelAndView) throws Exception;

    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                         Exception ex) throws Exception;
}
  • preHandle():Controller 方法执行前调用。返回 true 放行,返回 false 中断请求。
  • postHandle():Controller 方法执行后、视图渲染前调用。
  • afterCompletion():整个请求完成后调用,适合资源清理、统一日志。只要 preHandle() 返回 true,即使后续发生异常,afterCompletion() 也会执行。

5.3 执行流程

浏览器请求
  → Servlet 容器
  → Filter 链
  → DispatcherServlet
  → Interceptor preHandle
  → Controller
  → Interceptor postHandle
  → 视图渲染
  → Interceptor afterCompletion
  → 响应返回

5.4 应用场景

  • 登录校验(基于注解或路径匹配)
  • 权限控制(角色、权限)
  • 接口耗时统计
  • 日志记录(用户、参数、响应)
  • 参数预处理(如语言、时区)
  • 防止重复提交(结合 Redis)

5.5 代码示例

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = request.getHeader("Authorization");

        if (token == null || !token.startsWith("Bearer ")) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }

        // 校验 JWT,解析用户信息
        // 可将用户信息存入 ThreadLocal,便于后续使用
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) {
        // 清理 ThreadLocal,防止内存泄漏
    }
}

注册拦截器:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/login", "/api/register");
    }
}

多个拦截器执行顺序:

  • preHandle() 按注册顺序执行。
  • postHandle()afterCompletion() 按注册逆序执行。

5.6 特点

  • 属于 Spring MVC,能获取 HandlerMethod 信息,方便精细控制。
  • 天然支持注入 Spring Bean,如 RedisTemplateUserService
  • 不能直接修改请求,但可以通过 request.setAttribute() 传递数据。
  • 默认不拦截静态资源,但配置 /** 时需手动排除静态资源路径。
  • 只在 Spring MVC 处理的请求中生效,不依赖 Servlet 容器。

六、Filter 与 Interceptor 对比

对比维度FilterInterceptor
所属规范/框架Servlet 规范Spring MVC
依赖容器依赖 Servlet 容器不依赖容器,依赖 Spring MVC
执行时机进入 DispatcherServlet 之前DispatcherServlet 之后、Controller 之前
拦截范围所有请求,包括静态资源Spring MVC 处理的请求,可配置路径
能否修改请求/响应可以包装修改不能直接修改请求,可设置属性
能否获取 Spring Bean可以,但需要额外配置或委托天然支持注入
能否获取 Handler 信息不能可以获取 HandlerMethod
适用场景编码、CORS、安全过滤、通用日志登录鉴权、权限、业务日志、耗时统计
实现方式函数回调Java 反射/代理
执行顺序先执行后执行

七、总结与选型建议

会话跟踪

  • 传统服务端渲染 Web 应用,推荐使用 Session + Redis 实现会话共享。
  • 前后端分离、移动端、微服务、API 服务,推荐使用 JWT,但需注意:
  • 设置合理的过期时间;
  • 使用刷新令牌或黑名单机制缓解无法主动失效的问题;
  • 不在 Payload 中存放敏感信息;
  • 使用 HTTPS 并妥善保管密钥。

请求拦截

  • Filter 适合通用底层处理:字符编码、CORS、静态资源拦截、请求包装、安全过滤。
  • Interceptor 适合业务级处理:登录鉴权、权限控制、业务日志、耗时统计,能访问 Spring 容器和 Handler 元数据。

实际项目中,两者通常配合使用:Filter 处理通用请求,Interceptor 处理业务级拦截。

唯有极致沉淀,才能造就辉煌。
最后更新于 2026-08-20