Java 会话技术、Cookie、JWT令牌、过滤器Filter、拦截器Interceptor

一. 会话技术

  1. 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。再一次会话中包含多次请求和响应。

  2. 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自统一浏览器,以便在同一次会话的多次请求间共享数据。

  3. 会话跟踪方案:

(1) 客户端会话跟踪技术: Cookie

(2) 服务端会话跟踪技术:Session

(3) 令牌技术

二. Cookie(传统方案)

  1. 服务器端创建Cookie后,自动响应给浏览器,浏览器会将Cookie自动存储在浏览器本地,后续请求中Cookie自动携带到服务器。
java 复制代码
 //设置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();
    }

  1. 优点:HTTP协议中支持的技术

  2. 缺点:

(1) 移动端APP无法使用Cookie;

(2) 不安全,用户可以自己禁用Cookie;

(3) Cookie不能跨域;(协议、IP/域名、端口任意一个不同即为跨域)

三. Session(传统方案)

  1. 基于Cookie实现的,只不过Cookie中存储的是Session ID值。
java 复制代码
    @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);
    }

  1. 优点:存储在服务端,安全

  2. 缺点:服务器集群环境下无法直接使用Session(可能存储在一台服务器上,再另一台服务器无法获取);Cookie的缺点(因为是基于Cookie实现的,也可能被禁用和删除);

四. 令牌 (主流方案)

  1. 优点:

(1) 支持PC端、移动端

(2) 解决集群环境下的认证问题

(3) 减轻服务器存储压力

  1. 缺点:需要程序员自己编码实现(编码相对繁琐)

五. 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 + / ) 来表示二进制数据的编码方式。

  1. JWT令牌-生成/解析

(1) 引入jjwt的依赖

(2) 调用官方提供的工具类 Jwts 来生成或解析jwt令牌

java 复制代码
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
java 复制代码
  /*
    * 生成jwt令牌
    * */
    @Test
    public 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:第三部分则是基于秘钥和签名算法加密后的字符串

java 复制代码
 /*
    * 解析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令牌时使用的秘钥是配套的

java 复制代码
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组件支持

java 复制代码
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) 登录案例:

java 复制代码
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);

    }


}
  1. 执行流程:
  1. 拦截路径
java 复制代码
@WebFilter(urlPatterns = "/*")

|--------|--------------|---------------------|
| 拦截路径 | urlPatterns值 | 说明 |
| 拦截具体路径 | /login | 只有访问/login路径时,才会拦截 |
| 拦截目录 | /emps/* | 访问/emps下所有资源时,都会被拦截 |
| 拦截所有 | /* | 访问所有资源都会被拦截 |

  1. 过滤器链

一个web应用中,可以配置多个过滤器,这就形成了一个过滤器链(过滤器越多,性能就相对越低),多个过滤器时,按照类名依次执行

七. 拦截器 Interceptor

  1. 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,主要用来动态拦截控制器方法的执行。

  2. 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码

  3. 快速入门

(1) 定义拦截器,实现HandlerInterceptor接口,并实现其所有方法

(2) 注册拦截器

java 复制代码
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 执行了");
    }
}
java 复制代码
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("/**"); // 拦截所有请求
    }
}
  1. 登录令牌校验案例
java 复制代码
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;
    }
}
java 复制代码
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("/**"); // 拦截所有请求
    }
}
  1. 拦截器-拦截路径

如上述【登录令牌校验案例】中 配置上 .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 |

  1. 拦截器执行流程

Filter 与 Interceptor 的区别

  1. 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口

  2. 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源

相关推荐
fury_1232 小时前
tsfile.raw提示
java·前端·javascript
q***7482 小时前
Spring Boot环境配置
java·spring boot·后端
MATLAB代码顾问2 小时前
多种时间序列预测算法的MATLAB实现
开发语言·算法·matlab
superlls2 小时前
(Spring)Spring Boot 自动装配原理总结
java·spring boot·spring
m0_736927043 小时前
2025高频Java后端场景题汇总(全年汇总版)
java·开发语言·经验分享·后端·面试·职场和发展·跳槽
CodeAmaz3 小时前
自定义限流方案(基于 Redis + 注解)
java·redis·限流·aop·自定义注解
FAREWELL000753 小时前
Lua学习记录(3) --- Lua中的复杂数据类型_table
开发语言·学习·lua
Felix_XXXXL3 小时前
Plugin ‘mysql_native_password‘ is not loaded`
java·后端
IT北辰3 小时前
Python实现居民供暖中暖气能耗数据可视化分析(文中含源码)
开发语言·python·信息可视化