一、会话跟踪技术概述
HTTP 是无状态协议,每次请求都是独立的,服务器默认无法判断两次请求是否来自同一个用户。会话跟踪技术用于在多个请求之间维持用户状态。常见方案包括:
- Cookie
- Session
- JWT(JSON Web Token)
下面先简单介绍 Cookie 和 Session,再重点介绍 JWT,以及 Java Web 中常用的过滤器 Filter 和拦截器 Interceptor。
二、Cookie 与 Session
2.1 Cookie
定义
Cookie 是服务器通过 Set-Cookie 响应头写入浏览器、由浏览器保存的小型文本数据。浏览器后续请求同一域名时,会自动通过 Cookie 请求头携带。
关键属性
| 属性 | 说明 |
|---|---|
Name / Value | Cookie 的键值对 |
Domain | 作用域,指定哪些域名可以访问 |
Path | 指定哪些路径会携带该 Cookie |
Expires / Max-Age | 过期时间,不设置则为会话 Cookie |
HttpOnly | 禁止 JavaScript 读取,降低 XSS 风险 |
Secure | 仅允许 HTTPS 传输 |
SameSite | 限制跨站请求携带,用于 CSRF 防护 |
工作流程
- 用户首次访问,服务器通过
Set-Cookie: userId=abc; HttpOnly; Path=/写入 Cookie。 - 浏览器保存 Cookie。
- 后续请求自动携带
Cookie: userId=abc。 - 服务器读取并识别用户。
优缺点
- 优点:数据保存在客户端,减轻服务器存储压力。
- 缺点:大小约 4KB;可被用户禁用;默认明文存储,不适合保存敏感数据。
2.2 Session
定义
Session 是保存在服务器端的用户会话数据。每个会话有一个唯一标识 SessionId,通常通过 Cookie 传给客户端。
工作流程
- 用户登录成功,服务器创建 Session,保存用户信息,例如
session.setAttribute("user", user)。 - 服务器返回
Set-Cookie: JSESSIONID=xxxx; HttpOnly。 - 浏览器后续请求自动携带
JSESSIONID。 - 服务器根据
JSESSIONID找到对应 Session,读取用户信息。
存储方式
默认可存内存,也可持久化到 Redis、数据库等。生产环境通常使用 Redis 实现 Session 共享。
优缺点
- 优点:数据保存在服务端,安全性相对较高;可存储较大对象;服务端可主动销毁。
- 缺点:占用服务端内存/存储资源;分布式环境下需要额外做 Session 共享;依赖 Cookie 传递 SessionId。
2.3 Cookie 与 Session 对比
| 对比维度 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端浏览器 | 服务端 |
| 安全性 | 较低,数据可被篡改、查看 | 较高,数据在服务端 |
| 容量 | 约 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:主题,通常为用户 IDexp:过期时间iat:签发时间nbf:生效时间jti:令牌唯一 ID- 自定义声明:如
role、userId
注意: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 对比
| 对比维度 | JWT | Session |
|---|---|---|
| 状态 | 无状态 | 有状态 |
| 存储位置 | 客户端 | 服务端 |
| 扩展性 | 好,适合分布式 | 需要共享存储 |
| 主动失效 | 难 | 容易 |
| 安全性 | 需妥善保管,泄露风险高 | 服务端可控制 |
| 性能 | 服务端验签有 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,如
RedisTemplate、UserService。 - 不能直接修改请求,但可以通过
request.setAttribute()传递数据。 - 默认不拦截静态资源,但配置
/**时需手动排除静态资源路径。 - 只在 Spring MVC 处理的请求中生效,不依赖 Servlet 容器。
六、Filter 与 Interceptor 对比
| 对比维度 | Filter | Interceptor |
|---|---|---|
| 所属规范/框架 | 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 处理业务级拦截。
Comments NOTHING