Java 会话技术、Cookie、JWT令牌、过滤器Filter、拦截器Interceptor
一. 会话技术
1. 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。再一次会话中包含多次请求和响应。
2. 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自统一浏览器,以便在同一次会话的多次请求间共享数据。
3. 会话跟踪方案:
(1) 客户端会话跟踪技术: Cookie
(2) 服务端会话跟踪技术:Session
(3) 令牌技术
二. Cookie(传统方案)
1. 服务器端创建Cookie后,自动响应给浏览器,浏览器会将Cookie自动存储在浏览器本地,后续请求中Cookie自动携带到服务器。
//设置Cookie@GetMapping("/ce1")public Result cookie1(HttpServletResponse response){//设置Cookie/响应Cookieresponse.addCookie(new Cookie("login_username","lizhuangzhuang"));return Result.success();}//获取Cookie@GetMapping("/ce2")public Result cookie2(HttpServletRequest request){Cookie[] cookies = request.getCookies();for (Cookie cookie : cookies) {if(cookie.getName().equals("login_username")){//输出name为login_username的cookieSystem.out.println("login_username: "+cookie.getValue());}}return Result.success();}




2. 优点:HTTP协议中支持的技术
3. 缺点:
(1) 移动端APP无法使用Cookie;
(2) 不安全,用户可以自己禁用Cookie;
(3) Cookie不能跨域;(协议、IP/域名、端口任意一个不同即为跨域)


三. Session(传统方案)
1. 基于Cookie实现的,只不过Cookie中存储的是Session ID值。
@GetMapping("/sn1")public Result session1(HttpSession session){log.info("HttpSession-s1: {}", session.hashCode());//往session中存储数据session.setAttribute("loginUser", "lizhuangzhuang");return Result.success();}@GetMapping("/sn2")public Result session2(HttpSession session){log.info("HttpSession-s2: {}", session.hashCode());//从session中获取数据Object loginUser = session.getAttribute("loginUser");log.info("loginUser: {}", loginUser);return Result.success(loginUser);}


2. 优点:存储在服务端,安全
3. 缺点:服务器集群环境下无法直接使用Session(可能存储在一台服务器上,再另一台服务器无法获取);Cookie的缺点(因为是基于Cookie实现的,也可能被禁用和删除);
四. 令牌 (主流方案)
1. 优点:
(1) 支持PC端、移动端
(2) 解决集群环境下的认证问题
(3) 减轻服务器存储压力
2. 缺点:需要程序员自己编码实现(编码相对繁琐)
五. JWT令牌
1. 全称:JSON Web Token (https://jwt.io/),定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息
2. 组成:
第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256", "type":"JWT"}
第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如{"id":"1","username":"lizhuangzhuang"}
第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload融入,并加入制定秘钥,通过制定签名算法计算而来
Base64: 是一种基于64个可打印字符(A-Z a-z 0-9 + / ) 来表示二进制数据的编码方式。

3. JWT令牌-生成/解析
(1) 引入jjwt的依赖
(2) 调用官方提供的工具类 Jwts 来生成或解析jwt令牌
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
/** 生成jwt令牌* */@Testpublic void testJwt1(){Map<String,Object> map = new HashMap<>();map.put("name","lizhuangzhuang");map.put("age","18");// 生成签名算法String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256,"123456") //第一个参数指定签名算法 第二个为秘钥.addClaims(map) //添加自定义信息.setExpiration(new Date(System.currentTimeMillis()+3600*1000)) // 设置有效期 1小时.compact(); // 生成令牌System.out.println(jwt);}
秘钥可为字符串 或 base64编码




8WF4okYi-k94WttbNAxRl90HsO9TsaW4YeTzQ2SIYMI:第三部分则是基于秘钥和签名算法加密后的字符串
/** 解析jwt令牌* */@Testpublic void testJwt2(){String toket = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoibGl6aHVhbmd6aHVhbmciLCJhZ2UiOiIxOCIsImV4cCI6MTc2MjUzMjYwMH0.8WF4okYi-k94WttbNAxRl90HsO9TsaW4YeTzQ2SIYMI";Claims claims = Jwts.parser().setSigningKey("123456").parseClaimsJws(toket).getBody();System.out.println(claims);}

一旦令牌数据被篡改,解析令牌时会报错

令牌过期解析是也会报错

注意事项:JWT校验时使用的签名秘钥,必须和生成jwt令牌时使用的秘钥是配套的
package com.wyyzs.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import java.util.Date;
import java.util.Map;/*
* jwt令牌工具类
* */
public class JwtUtils {// 秘钥private static String signKey = "bGl6aHVhbmd6aHVhbmc=";// 有效期 1小时private static Long expire = 3600000L;/*** 生成JWT令牌* @return*/public static String generateJwt(Map<String,Object> claims){String jwt = Jwts.builder().addClaims(claims).signWith(SignatureAlgorithm.HS256, signKey).setExpiration(new Date(System.currentTimeMillis() + expire)).compact();return jwt;}/*** 解析JWT令牌* @param jwt JWT令牌* @return JWT第二部分负载 payload 中存储的内容*/public static Claims parseJWT(String jwt){Claims claims = Jwts.parser().setSigningKey(signKey).parseClaimsJws(jwt).getBody();return claims;}
}
用户登录成功后,系统会自动下发JWT令牌,然后在后续的每次请求中,都需要在请求头hrader中携带到服务端,请求头的名称为token,值为登录时下发的JWT令牌,如果检测到用户未登录,则提示错误信息。
六. 过滤器 Filter
1. Filter过滤器:是javaWeb三大组件(Servlet、Filter、Listtener)之一。
2. 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
3. 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。
4. 快速入门
(1) 定义Filter:定义一个类,实现Filter接口,并实现其所有方法
(2) 配置Filter:Filter类上加@WebFilter注解,配置拦截路径.引导类上加@ServletComponentScan 开启Servlet组件支持
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.annotation.WebServlet;
import lombok.extern.slf4j.Slf4j;import java.io.IOException;@WebFilter(urlPatterns = "/*") // 拦截所有请求
@Slf4j
public class webFilter implements Filter {/** 初始化方法,web服务器启动时执行,只执行一次* 一般做一些资源/环境的准备工作* 需要时实现 不需要时可不实现 接口中已经默认实现* */@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.info("过滤器初始化init方法执行了.....");}/** 拦截到请求之后执行 会执行多次* */@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {log.info("拦截到了请求");// 放行操作 不写服务器则不会返回结果filterChain.doFilter(servletRequest, servletResponse);log.info("放行后逻辑执行");}/** 销毁方法,web服务器关闭的时候执行,只执行一次* 一般做一些资源释放/环境清理工作* 需要时实现 不需要时可不实现 接口中已经默认实现* */@Overridepublic void destroy() {log.info("过滤器销毁方法destroy执行了.....");}
}



(3). 注意事项:如果过滤器中不执行放行操作,过滤器拦截到请求之后,就不会访问对应的资源;放行:filterChain.doFilter(servletRequest, servletResponse);
(4) 登录案例:
import com.wyyzs.utils.JwtUtils;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;import java.io.IOException;@Slf4j
@WebFilter(urlPatterns = "/*")
public class TokenFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// 1. 获取请求路径HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;String url = request.getRequestURI();// 2. 判断是否登录请求 是登录则放行if (url.contains("/login")) {log.info("登录操作--放行");filterChain.doFilter(servletRequest, servletResponse);return;}// 3.获取请求头中的tokenString token = request.getHeader("token");// 4. 判断Token是否存在,不存在 说明没登录if (token == null || token.isBlank()) {log.info("令牌token为空");// 设置返回状态码 SC_UNAUTHORIZED 401 校验未通过response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);return;}// 5. 如果token存在 校验令牌 如果校验失败 则返回错误提示try{JwtUtils.parseJWT(token);} catch (Exception e){log.info("令牌非法");// 设置返回状态码 SC_UNAUTHORIZED 401 校验未通过response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);return;}// 6. 校验通过 放行log.info("校验通过 放行");filterChain.doFilter(servletRequest, servletResponse);}}
5. 执行流程:

6. 拦截路径
@WebFilter(urlPatterns = "/*")
| 拦截路径 | urlPatterns值 | 说明 |
| 拦截具体路径 | /login | 只有访问/login路径时,才会拦截 |
| 拦截目录 | /emps/* | 访问/emps下所有资源时,都会被拦截 |
| 拦截所有 | /* | 访问所有资源都会被拦截 |
7. 过滤器链
一个web应用中,可以配置多个过滤器,这就形成了一个过滤器链(过滤器越多,性能就相对越低),多个过滤器时,按照类名依次执行
七. 拦截器 Interceptor
1. 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,主要用来动态拦截控制器方法的执行。
2. 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码
3. 快速入门
(1) 定义拦截器,实现HandlerInterceptor接口,并实现其所有方法
(2) 注册拦截器
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;@Slf4j
@Component // 由拦截器是spring提供的技术 加@Component注解 交给ioc容器管理
public class webInterceptor implements HandlerInterceptor {/** 目标资源方法执行前执行,返回 true则放行 返回false则不放行* */@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("拦截器preHandle 执行了");return true;}/** 目标资源方法执行后执行* */@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("拦截器postHandle 执行了");}/** 视图渲染完毕后执行 最后执行 (目前大部分项目为前后端分离的项目 一般用不到)* */@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("拦截器afterCompletion 执行了");}
}
import com.wyyzs.Interceptor.webInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*
* 配置类
* 注册拦截器
* */
@Configuration // 代表这个类为配置类
public class webConfig implements WebMvcConfigurer {@Autowiredprivate webInterceptor webInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(webInterceptor).addPathPatterns("/**"); // 拦截所有请求}
}
4. 登录令牌校验案例
import com.wyyzs.utils.JwtUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {/** 登录校验 目标资源执行前校验* */@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1. 获取请求路径String url = request.getRequestURI();// 2. 判断是否登录请求 是登录则放行if (url.contains("/login")) {log.info("登录操作--放行");return true;}// 3.获取请求头中的tokenString token = request.getHeader("token");// 4. 判断Token是否存在,不存在 说明没登录if (token == null || token.isBlank()) {log.info("令牌token为空");// 设置返回状态码 SC_UNAUTHORIZED 401 校验未通过response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);return false;}// 5. 如果token存在 校验令牌 如果校验失败 则返回错误提示try{JwtUtils.parseJWT(token);} catch (Exception e){log.info("令牌非法");// 设置返回状态码 SC_UNAUTHORIZED 401 校验未通过response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);return false;}// 6. 校验通过 放行log.info("校验通过 放行");return true;}
}
import com.wyyzs.Interceptor.TokenInterceptor;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*
* 配置类
* 注册拦截器
* */
@Configuration // 代表这个类为配置类
public class webConfig implements WebMvcConfigurer {@Autowiredprivate TokenInterceptor tokenInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tokenInterceptor).addPathPatterns("/**"); // 拦截所有请求}
}
5. 拦截器-拦截路径

如上述【登录令牌校验案例】中 配置上 .excludePathPatterns("/login") 则在 TokenInterceptor中不需要再执行【1、2】校验是否登录操作
| 拦截路径 | 说明 | |
| /* | 一级路径 | 能匹配/deps /login 不能匹配 deps/1 |
| /** | 任意路径 | 能匹配/deps /login deps/1 |
| /deps/* | /depts 下的一级路径 | 能匹配 /deps/1 不能匹配 /deps /deps/1/2 |
| /deps/** | /depts 下的任意路径 | 能匹配 /deps /deps/1/2 不能匹配 /login/1 |
6. 拦截器执行流程

Filter 与 Interceptor 的区别
1. 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口
2. 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源
