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/响应Cookie
        response.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的cookie
                System.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令牌

        
            io.jsonwebtoken
            jjwt
            0.9.1
        
  /*
    * 生成jwt令牌
    * */
    @Test
    public void testJwt1(){
        Map 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令牌
    * */
    @Test
    public 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 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服务器启动时执行,只执行一次
    *  一般做一些资源/环境的准备工作
    *   需要时实现 不需要时可不实现 接口中已经默认实现
    * */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
       log.info("过滤器初始化init方法执行了.....");
    }
    /*
    * 拦截到请求之后执行 会执行多次
    * */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("拦截到了请求");
        // 放行操作 不写服务器则不会返回结果
         filterChain.doFilter(servletRequest, servletResponse);
        log.info("放行后逻辑执行");
    }
    /*
     * 销毁方法,web服务器关闭的时候执行,只执行一次
     *   一般做一些资源释放/环境清理工作
     *   需要时实现 不需要时可不实现 接口中已经默认实现
     * */
    @Override
    public 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 {
    @Override
    public 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.获取请求头中的token
        String 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则不放行
    * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("拦截器preHandle 执行了");
       return true;
    }
    /*
    *  目标资源方法执行后执行
    * */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("拦截器postHandle 执行了");
    }
    /*
    * 视图渲染完毕后执行 最后执行 (目前大部分项目为前后端分离的项目 一般用不到)
    * */
    @Override
    public 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 {
    @Autowired
    private webInterceptor webInterceptor;
    @Override
    public 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 {
    /*
    * 登录校验 目标资源执行前校验
    * */
    @Override
    public 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.获取请求头中的token
        String 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 {
    @Autowired
    private TokenInterceptor tokenInterceptor;
    @Override
    public 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环境中的资源

posted @ 2026-01-23 12:39  gccbuaa  阅读(2)  评论(0)    收藏  举报