过滤器与拦截器 - 登录校验与登录认证

news2024/11/15 7:26:10

文章目录

  • 登录校验与登录认证
  • 一、登录认证
    • 1.1 基础登录功能
    • 1.2 会话技术
      • 1.2.1 介绍
      • 1.2.2 方案一 Cookie
        • 1.2.2.1 基本介绍
        • 1.2.2.2 服务端向浏览器响应Cookie
        • 1.2.2.3 浏览器向服务端请求携带Cookie
      • 1.2.3 方案二 Session
        • 1.2.3.1 基本介绍
        • 1.2.3.2 服务端向浏览器响应Session
        • 1.2.3.3 浏览器向服务端请求携带Session
  • 二、JWT 令牌技术 - 主流
    • 2.1 基本介绍
    • 2.2 JWT令牌 生成
      • 2.2.1 Maven
      • 2.2.2 生成JWT令牌
    • 2.3 JWT令牌 校验
    • 2.4 登录成功下发令牌
      • 2.4.1 封装工具类
      • 2.4.2 发放JWT令牌
  • 三、 Filter
    • 3.1 基本介绍
    • 3.2 Filter 快速入门
    • 3.3 执行流程
    • 3.4 拦截路径
    • 3.5 过滤器链
    • 3.6 登录校验过滤器
      • 3.6.1 实现思路
      • 3.6.2 代码实现
  • 四、Interceptor - 拦截器

登录校验与登录认证

一、登录认证

1.1 基础登录功能

接口

@Slf4j
@RestController
public class LoginController {
    @Autowired
    private EmpService empService;

//  用Emp对象接收用户名和密码,里面将属性封装好了
    @PostMapping("/login")
    public Result login(@RequestBody Emp emp) {
        log.info("员工登录{}",emp);
        Emp e = empService.login(emp);

        return e!=null? Result.success() :Result.error("用户名或密码错误");
    }
}

SQL

@Select("select  * from emp where username =#{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);

1.2 会话技术

1.2.1 介绍

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

比如我们打开浏览器与Web服务器建立连接。首先访问login接口,再访问depts接口,最后访问emps接口。只要浏览器和服务器都没有关闭,那么这三次请求都是在一次会话中完成的。

关闭服务器,所有的会话都会关闭

关闭当前浏览器,当前会话结束

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

http请求是无状态的,下一次请求并不会携带上一次请求的数据,每一次请求都是相互独立额,也就保证了http协议它的效率是比较高的。由于是无状态的,我们也无法确定两次请求是否来自于同一个浏览器,是否来自于同一个会话。此时同一个会话的多个请求之间是没有办法共享数据的。

我们要想解决这个问题,就需要会话跟踪技术。

会话跟踪方案

  • 客户端会话跟踪技术:Cookie 存储在客户端浏览器中
  • 服务端会话跟踪技术:Session 存储在服务器当中
  • 令牌技术

Cookie 与Session 是传统web开发当中所提供的的两种会话跟踪技术,而当前企业开发中最主流的是令牌技术

1.2.2 方案一 Cookie

1.2.2.1 基本介绍

存储在客户端浏览器,使用Cookie跟踪对话,就可以在浏览器第一次发起请求来请求浏览器的时候设置一个Cookie。

我们在Cookie中可以存储一些信息,比如存储用户名,用户id。服务端在给客户端响应数据的时候会自动将Cookie响应给浏览器。

浏览器接收到服务端响应的Cookie后将其存储在浏览器本地。

之后客户端每次发请求都会将本地存储的Cookie携带到服务端。我们在服务端就可以获取用户的信息,也可以判断Cookie是否存在。

如果不存在说明这个客户端之前没有访问登录接口;如果存在说明这个客户端之前已经登录完成了。

这样我们就能在同一次会话的不同请求之前来共享数据

三个自动

  • 服务器自动将Cookie响应给浏览器
  • 浏览器接收到响应回来的数据之后会自动的将Cookie存储在浏览器本地
  • 后续请求中浏览器会自动将Cookie携带到服务端

为什么上面这个三个时自动进行的呢?

因为Cookie是http协议支持的内容,各大浏览器厂商都支持了这一标准。

在HTTP协议中,提供了响应头(setCookie)和一个请求头

HTTP相关技术文档

https://cloud.tencent.com/developer/doc/1117

服务端在给浏览器响应Cookie的时候是以哪种方式响应回去的?

直接设置了一个响应头Set-Cookie,请求头所对应的数据就是Cookie对应的值。

“name”就是Cookie的名称,“value”就是Cookie的值

此响应头返回给浏览器,浏览器会自动解析响应头

image-20230516111420677

优点

  • HTTP 协议中支持的技术

    缺点

  • 移动端APP无法使用Cookie

  • 不安全,用户可以自己禁用Cookie

  • Cookie不能跨域(协议、IP/域名、端口号,这三者有一个不同就是跨域)

1.2.2.2 服务端向浏览器响应Cookie

//设置Cookie - 服务器要给浏览器响应数据
@GetMapping("/c1")
public Result cookie1(HttpServletResponse response){
    response.addCookie(new Cookie("login_username","itheima")); //设置Cookie/响应Cookie
    return Result.success();
}

效果图

image-20230516112139072

自动保存在下图位置

image-20230516112255691

1.2.2.3 浏览器向服务端请求携带Cookie

服务端解析从浏览器向服务端请求携带的Cookie

//获取Cookie
@GetMapping("/c2")
public Result cookie2(HttpServletRequest request){
    Cookie[] cookies = request.getCookies();
    for (Cookie cookie : cookies) {
        if(cookie.getName().equals("login_username")){
            System.out.println("login_username: "+cookie.getValue()); //输出name为login_username的cookie
        }
    }
    return Result.success();
}

1.2.3 方案二 Session

1.2.3.1 基本介绍

服务器端会话跟踪技术,存储在服务器端,底层借助Cookie实现。

浏览器在第一次请求服务器时,我们可以直接在服务器当中获取到会话对象Session(第一次请求会话对象Session是不存在的,服务器会自动创建一个会话对象),每一个会话对象都有一个id。

之后服务端再给浏览器响应数据的时候,会将Session的id通过Cookie响应给浏览器,其实就是在响应头重增加了一个Set-Cookie。

在这里Cookie的名字是固定的,就是JSESSIONID,代表的就是服务端会话对象Session的id。

浏览器接收到数据之后会自动将这个Cookie存储在浏览器本地,之后再请求服务端时都会将Cookie数据获取出来并且携带到服务端。

浏览器拿到JSESSIONID也就是Session的id后,会从众多会话对象Session中找到当前请求对应的绘画对象Session。

找到对应Session之后,就可以实现在同一次会话的多次请求之间来共享数据

image-20230516114347168

优点

  • 存储在服务端,安全

缺点

  • 服务器集群环境下无法直接使用Session
  • Cookie的缺点(底层Cookie实现)

1.2.3.2 服务端向浏览器响应Session

//  往HTTPSession中存储值
//  服务器会判断当前这次请求对应的会话对象Session是否存在,
//      如果不存在会新创建一个Session,如果存在会获取当前这一次请求对应的Session
    @GetMapping("/s1")
    public Result session1(HttpSession session){
        log.info("HttpSession-s1: {}", session.hashCode());//HttpSession-s1: 1750219908

        session.setAttribute("loginUser", "tom"); //往session中存储数据
        return Result.success();
    }

Set-Cookie

image-20230516115318650

Application

image-20230516115358133

1.2.3.3 浏览器向服务端请求携带Session

//  这个地方我们可以声明HttpSession对象,也可以使用HttpServletRequest对象
    @GetMapping("/s2")
    public Result session2(HttpServletRequest request){
        HttpSession session = request.getSession();//拿到当前这次请求对应的会话对象
        log.info("HttpSession-s2: {}", session.hashCode());  //HttpSession-s2: 1750219908

        Object loginUser = session.getAttribute("loginUser"); //从session中获取数据
        log.info("loginUser: {}", loginUser);  //loginUser: tom
        return Result.success(loginUser);
    }

image-20230516115843757

注意看上面的几张图,页面中完全没有涉及到“loginUser”的存储,那为什么控制台还能输出“loginUser: tom”?

​ Session存储在服务器端,每次请求浏览器都会写到Cookie,并且有JSESSIONID,服务端获取到之后就可以解析,确定是哪个Session对象

而且观察控制台SessionID,确实是同一个

image-20230516120147814

二、JWT 令牌技术 - 主流

用户身份表示

存储在客户端。不用担心安全问题,浏览器携带令牌请求时,服务端会进行校验。

无效的,返回错误结果;有效的,访问对应权限。

优点

  • 支持PC端、移动端
  • 解决集群环境下的认证问题
  • 减轻服务器端存储压力

缺点

  • 需要自己实现

2.1 基本介绍

官方网站:JSON Web Tokens - jwt.io

简洁、自包含的格式,用于在通信双方以JSON数据格式安全的传输信息。由于数字签名的存在,这些信息都是可靠的。

组成:

  • 第一部分:Header(头),记录令牌类型、签名算法等。

    例如:[“alg”:“HS256”,“type”:"WT”)

  • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。

    例如:“id”."1"“username”."Tom”

  • 第三部分: Signature(签名),防止Token被篡改、确保安全性。

    ​ 将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

    ​ 第三部分字符的由来不是Base64编码方式生成的,而是前面指定的签名算法

image-20230516122822444

JSON数据格式是怎么变成一些字符的呢?

通过Base64编码方式

应用场景

​ 登录认证。

① 用户登录成功后,服务端生成一个JWT令牌,并传输给前端。

②前端拿到JWT令牌之后会将其存储起来,之后前端的每一次请求都会将JWT令牌携带到服务端,服务端会对请求进行统一拦截,拦截之后先判断有没有把这个令牌带过来。

​ 如果没有令牌就拒绝访问;若有令牌但是无效仍然拒绝访问;若有效则直接放心,并处理对应请求

2.2 JWT令牌 生成

2.2.1 Maven

无论生成和校验都需要下面的工具类

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

2.2.2 生成JWT令牌

签名算法具体有哪些,可以查看官网

image-20230516124444272

    /**
     * 生成JWT
     */
    @Test
    public void testGenJwt() {
        Map<String, Object> map = new HashMap<>();
        map.put("id", 1);
        map.put("name", "tom");

//      链式编程 - Jwt令牌在生成的时候所需要设置的一些参数
        String jwt = Jwts.builder()
//              存储在第一个部分         参数一 数字签名算法  参数二 秘钥
                .signWith(SignatureAlgorithm.HS256, "zhangjingqi")
//               JWT令牌所存储的内容(自定义数据,存储在第二个部分,原始自定义数据是JSON格式)
//               可以是Map集合,也可以是Claims对象
                .setClaims(map)
//               设置令牌有效期 - 一个小时后过期, 因为是毫秒,3600*1000代表一个小时
                .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))
//               调用compact会有一个String返回值,就是JWT令牌
                .compact();

        System.out.println(jwt);//eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTY4NDIxNjUzNX0.s33vECyehznKMrbDqD1Pdx-DrHWkscdyNeWmLnY-ArU

    }

将生成的JWT放在官网进行解码,和我们的数据一模一样

image-20230516125816351

2.3 JWT令牌 校验

Jwt令牌生成后,不论我们改哪部分的字符,在解析的时候都会报错,所以Jwt令牌是很安全的。

令牌时间过期之后也不能访问

    @Test
    public void testParseJwt(){
        Claims claims = Jwts.parser()
//               指定签名秘钥
                .setSigningKey("zhangjingqi")
//               解析JTW令牌
                .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTY4NDIxNjUzNX0.s33vECyehznKMrbDqD1Pdx-DrHWkscdyNeWmLnY-ArU")
//               拿到了我们自定义的内容,也就是Jwt令牌的第二个部分
                .getBody();
        System.out.println(claims); //{name=tom, id=1, exp=1684216535}
    }

注意事项

  • JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的(编码和解析时秘钥必须相同

  • 如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或失效,令牌非法

2.4 登录成功下发令牌

  • 令牌生成: 登录成功后,生成JWT令牌,返回给前端
  • 令牌校验,在请求到达服务端后,对令牌进行统一拦截、校验

用户登录成功后,系统会自动下发JWT令牌,然后在后续的每次请求中,都需要在请求头header中携带到服务端,请求头的名称为token,值为登录时下发的JWT令牌

2.4.1 封装工具类

将上面编码和解码汇编成一个工具类供我们使用

public class JwtUtils {

    private static String signKey = "zhangjingqi";
    private static Long expire = 43200000L;

    /**
     * 生成JWT令牌
     * @param claims JWT第二部分负载 payload 中存储的内容
     * @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;
    }
}

2.4.2 发放JWT令牌

//  用Emp对象接收用户名和密码,里面将属性封装好了
    @PostMapping("/login")
    public Result login(@RequestBody Emp emp) {
        log.info("员工登录{}",emp);
        Emp e = empService.login(emp);

//      登陆成功,生成令牌并下发令牌
        if(e!=null){
            Map<String, Object> claims = new HashMap<>();
            claims.put("id",e.getId());
            claims.put("name",e.getName());
            claims.put("username",e.getUsername());
//          生成令牌,员工信息已经在里面了
            String jwt = JwtUtils.generateJwt(claims);
            return Result.success(jwt);
        }

//      登录失败,返回错误信息
        return Result.error("用户名或密码错误");
    }

image-20230516141020710

网页联调

接收编码后,前端人员将其存储到Local Storage中

image-20230516142415731

这里面的Key “tlias_token”是前端自己定义的

image-20230516142556341

随便抓取一个请求,看此时是否携带Jwt令牌

显然是存在的。这个地方的实现是前端完成的,后端不需要管

image-20230516142855990

三、 Filter

Filter过滤器,是javaWeb三大组件(Servlet、Filter、Listener)之一

3.1 基本介绍

概念:

  • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能

​ ① 想要访问服务器中的某些资源,必须先经过Filter过滤器

​ ② 再此处进行一些操作,完成之后进行放行,访问对应的资源

​ ③ 资源访问完毕,最后再回到过滤器,然后再给浏览器响应对应的数据

  • 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。

​ 如果没有过滤器,我们需要在每一个接口中编写登录校验逻辑。

如果登录了,放行去访问对应的信息,如果没有登录,直接在Filter中返回错误信息,不再访问后面的请求

3.2 Filter 快速入门

  • 定义Filter:定义一个类,实现Filter接口,并重写其所有方法

    但是开发中我们一般只实现doFilter这个方法,其他两个方式使用默认实现即可

//  定义Filter,并重写三个方法
//  在web服务器启动的时候,会自动创建Filter过滤器对象
//不配置的话过滤器不会生效,urlPatterns表示拦截什么样的请求,/*代表拦截所有请求
@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {

//  初始化方法,过滤器创建完毕之后会自动调用init方法,只会调用一次(只会在创建时调用一次)
//  一般在这里完成一些资源及环境的准备操作
    public void init(FilterConfig filterConfig) throws ServletException {
//        Filter.super.init(filterConfig);
        System.out.println("init 初始化方法执行了");
    }


//  每一次拦截到请求都会调用的方法,最为重要的方法,是会被调用多次
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("拦截方法执行,拦截到了请求 ...");
//      往下放行去访问对应的资源,如果不放行,页面获取不到对应数据(接口资源是获取不到的)
        chain.doFilter(request, response);
    }

//  销毁方法。服务区关闭时调用,只调用一次
//  一般在这里完成资源的释放和环境的清理操作
    public void destroy() {
//        Filter.super.destroy();
        System.out.println("destroy 销毁方法执行了 ");
    }

}
  • 配置Filter:Filter类上添加@WebFilter注解,配置拦截资源路径。引导类上添加@ServletComponentScan注解开启Servlet组件支持
//Filter是javaweb三大组件之一,不是Spring提供的,如果想要使用三大组件,需要添加这个注解
@ServletComponentScan
@SpringBootApplication
public class SpringbootWebApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebApplication.class, args);
    }

}

启动程序,观察控制台

init方法只会执行这一次

image-20230516154156916

随意访问几个接口,观察控制台

image-20230516154417765

3.3 执行流程

image-20230516160151214

当我们的过滤器拦截到请求之后我们需要完成一个非常重要的操作,那就是放行。

chain.doFilter(request, response);

在过滤器放行之前我们可以执行一段逻辑。

”放行“就是让其去访问对应的web资源,访问完过滤器之后还会回到过滤器当中,执行“放行”语句后面的代码,执行完毕之后再给浏览器响应数据

chain.doFilter(request, response);
System.out.println("执行放行后逻辑 ...");
  • 放行后访问对应资源,资源访问完成后还会回到Filter中吗?

​ 会

  • 如果回到Filter中,是重新执行还是执行放行后的逻辑?

​ 执行放行后的逻辑

3.4 拦截路径

在快速入门中配置的是 /*,代表拦截所有请求

@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {}
拦截路径urlPatterns含义
拦截具体路径/login只访问/login路径时才会被拦截
目录拦截/emps/*访问/emps下的所有资源,都会被拦截
拦截所有/*访问所有资源,都会被拦截

3.5 过滤器链

  • 一个web应用中,可以配置多个过滤器,多个过滤器形成了一个过滤器链。

​ 过滤器链中的过滤器会一个一个的执行,第一个放行之后会执行第二个,依次推,最后一个过滤器执行完后会访问对应请求。

过滤器链中最后一个过滤器放行的话,会放行到web资源当中来访问web资源

​ 访问完资源后,是倒着进行的,先执行最后一个过滤器,再倒数第二个…

image-20230516161012600

  • 注解配置Filter,优先级是按照过滤器类型(字符串)的自然排序

创建新的过滤器

@WebFilter(urlPatterns = "/*")
public class AbcFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Abc拦截方法执行,拦截到了请求 ...");

        filterChain.doFilter(servletRequest, servletResponse);

        System.out.println("Abc执行放行后逻辑 ...");
    }
}

控制台输出:(我们之前创建了一个DemoFilter过滤器)

Abc拦截方法执行,拦截到了请求 …
Demo拦截方法执行,拦截到了请求 …

Demo执行放行后逻辑 …
Abc执行放行后逻辑 …

3.6 登录校验过滤器

备注说明:

用户登录成功后,系统会自动下发JWT令牌,然后在后续的每次请求中,都需要在请求头header中携带到服务端,请求头的名称为token,值为登录时下发的JWT令牌。

如果检测到用户未登录,则会返回如下固定错误信息。

  • 所有的请求,拦截到之后,都需要校验令牌吗?

​ 登录请求例外

  • 拦截到请求后,什么情况下可以放行,执行业务操作?

​ JWT令牌存在且令牌校验通过(合法),否则都会返回未登录错误结果

3.6.1 实现思路

  • 获取请求url
  • 判断请求url中是否包含login,如果包含,说明是登录操作,放行
  • 获取请求头中的令牌(token)
  • 判断令牌是否存在,如果不存在,返回错误结果(未登录)
  • 解析token,如果解析失败,返回错误结果(未登录)
  • 放行

3.6.2 代码实现

在过滤器当中为什么要把ServletRequest类型强转成HttpServletRequest类型?

在Java中,ServletRequest是一个接口,它是由Servlet容器提供的。HttpServletRequest接口则是ServletRequest接口的子接口,它包含了一些用于HTTP协议的方法和属性。在Java Web应用程序中,Servlet容器实现了ServletRequest和HttpServletRequest接口,并使用HttpServletRequest实现了HTTP协议相关的逻辑。

在开发Web应用程序时,Servlet容器将在每个客户端请求到达时创建一个ServletRequest对象并将其传递给请求处理器。由于具体的实现是由Servlet容器提供的并且通常是HttpServletRequest,因此在编写Servlet处理器时,我们通常将ServletRequest对象强制转换成HttpServletRequest对象,以便能够调用提供的HTTP协议相关方法。

因此,在过滤器中,如果我们需要使用HttpServletRequest接口中特定的HTTP协议相关方法,我们需要将ServletRequest对象强制转换成HttpServletRequest对象。这样我们才能够在处理ServletRequest对象时,使用HttpServletRequest中更多的方法和属性。

注意!!!将刚刚测试的Filter注释掉,只留这一个过滤器!!!!!!!!

@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//      ServletRequest、ServletResponse是父类,
//      请求对象与响应对象
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        //  TODO 1.获取请求url
        String requestURL = request.getRequestURL().toString(); //不toString就是StringBuffer类型
        log.info("请求的url:{}", requestURL);

        //  TODO 2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
        if (requestURL.contains("/login")){
            log.info("登录操作,放行...");
            filterChain.doFilter(request, response);
//           登录操作不需要执行下面的逻辑,直接结束此过滤器即可
            return;
        }

        //  TODO 3.获取请求头中的令牌(token)
        String token = request.getHeader("token");

        //  TODO 4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if (!StringUtils.hasLength(token)) { //spring当中的工具类
//            说明字符串为null,返回错误结果(未登录)
            log.info("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
//          手动转JSON
            String errorJson = JSON.toJSONString(error);
//          response.getWriter()获取输出流,write()直接将数据响应给浏览器
            response.getWriter().write(errorJson);
            return;
        }

        //  TODO 5.解析token,如果解析失败,返回错误结果(未登录)
//      说明存在令牌,校验
        try{
            Claims claims = JwtUtils.parseJWT(token);
        }catch (Exception e){ // 出现异常代表着解析失败
            e.printStackTrace();
            log.info("解析令牌失败,返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
//          手动转JSON
            String errorJson = JSON.toJSONString(error);
//          response.getWriter()获取输出流,write()直接将数据响应给浏览器
            response.getWriter().write(errorJson);
            return;
        }
//       到这里说明令牌解析成功,直接放行
        //  TODO 6.放行
        log.info("令牌合法,放行");
        filterChain.doFilter(request, response);
    }
}

很完美

image-20230516182613212

四、Interceptor - 拦截器

之前做的笔记: Springboot——拦截器_springboot 拦截器_

概念:是一种动态拦截方法调用调用机制,类似过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行(拦截请求的)

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

image-20230516184503393

4.1 入门

在测试之前可以把过滤器关掉,否则一直验证token比较麻烦

  • 定义拦截器,实现HandlerInterceptor接口,并且重写其所有方法
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
//  目标资源方法执行前执行(Controller方法执行之前), true:放行, false:不放行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle......");
//        return HandlerInterceptor.super.preHandle(request, response, handler);
        return true;
    }

//  目标资源方法执行后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle.....");
//        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

//  视图渲染完毕后执行,最后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        System.out.println("afterCompletion......");
    }
}
  • 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor)
                .addPathPatterns("/**");
    }
}

4.2 详解

4.2.1 拦截路径

addPathPatterns 指定拦截哪些路径

excludePathPatterns 执行不拦截哪些不经

registry.addInterceptor(loginCheckInterceptor)
        .addPathPatterns("/**")
        .excludePathPatterns("/login");
拦截路径含义举例
/*一级路径能匹配/depts,/emps,/login,不能匹配/depts/1
/**任意级路径能匹配/depts/1,/depts,/depts/1/2
/depts/*/depts下一级路径能匹配/depts/1,不能匹配/depts,/depts/1/2,/depts
/depts/**/depts下的任意路径能匹配/depts,/depts/1,/depts,/depts/1/2

4.2.2 执行流程

① 当浏览器向Web服务器发送请求时,我们所设定的过滤器会拦截到这一请求。

② 过滤器先执行放行前逻辑,在此处决定是否放行

③ 放行之后进入到Spring环境中,进入DispatcherServlet

​ 在请求响应时说道,tomcat服务器并不识别我们所编写的controller程序,但是他是识别Servlet程序的,因为tomcat是一个Servlet程序。

​ 而在SpringWeb当中提供了一个非常核心的Servlet,我们叫做DispatcherServlet前端核心控制器,所以请求会先进入到DispatcherServlet,请求由DispatcherServlet再传给Controller

如果设置了拦截器的话,DispatcherServlet在传给Controller之前需要先被拦截器拦截住

④ 拦截器拦截到,先进行preHandle,决定是否放行,如果放行便访问Controller层方法

⑤Controller层方法完成之后,再执行postHandler方法已经afterCompletion方法

⑥返回给DispatcherServlet

⑦最终执行放行后逻辑

⑧最终给浏览器响应数据

image-20230517102927383

4.2.3 过滤器与拦截器的区别

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

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

4.3 登录校验拦截器

4.3.1 实现思路

与过滤器思路一模一样

  • 获取请求url
  • 判断请求url中是否包含login,如果包含,说明是登录操作,放行
  • 获取请求头中的令牌(token)
  • 判断令牌是否存在,如果不存在,返回错误结果(未登录)
  • 解析token,如果解析失败,返回错误结果(未登录)
  • 放行

4.3.2 代码实现

测试之前记得吧过滤器注解注释掉

 //  目标资源方法执行前执行(Controller方法执行之前), true:放行, false:不放行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //  TODO 1.获取请求url
        String requestURL = request.getRequestURL().toString(); //不toString就是StringBuffer类型
        log.info("请求的url:{}", requestURL);

        //  TODO 2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
        if (requestURL.contains("/login")) {
            log.info("登录操作,放行...");
            return true;
        }

        //  TODO 3.获取请求头中的令牌(token)
        String token = request.getHeader("token");

        //  TODO 4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if (!StringUtils.hasLength(token)) { //spring当中的工具类
//            说明字符串为null,返回错误结果(未登录)
            log.info("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
//          手动转JSON
            String errorJson = JSON.toJSONString(error);
//          response.getWriter()获取输出流,write()直接将数据响应给浏览器
            response.getWriter().write(errorJson);
            return false;
        }

        //  TODO 5.解析token,如果解析失败,返回错误结果(未登录)
//      说明存在令牌,校验
        try {
            Claims claims = JwtUtils.parseJWT(token);
        } catch (Exception e) { // 出现异常代表着解析失败
            e.printStackTrace();
            log.info("解析令牌失败,返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
//          手动转JSON
            String errorJson = JSON.toJSONString(error);
//          response.getWriter()获取输出流,write()直接将数据响应给浏览器
            response.getWriter().write(errorJson);
            return false;
        }
//       到这里说明令牌解析成功,直接放行
        //  TODO 6.放行
        log.info("令牌合法,放行");
//        return HandlerInterceptor.super.preHandle(request, response, handler);
        return true;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/535928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何把pdf拆分成一页一页?三个方法试试看

PDF文件作为常用的文件&#xff0c;其功能性较为全面。在日常工作过程中&#xff0c;PDF文件格式便于传输&#xff0c;且pdf文件不易被修改&#xff0c;能够增强文件的安全性和有效性。而在很多时候&#xff0c;为了能够快速地将PDF进行传输、保存等&#xff0c;需要对一些pdf文…

3D模型处理实战【Open3D】

在本文中&#xff0c;我们将学习如何使用 Python 的 Open3D 库探索、处理和可视化 3D 模型。 如果你正在考虑为特定任务处理 3D 数据/模型&#xff0c;例如为 3D 模型分类和/或分割训练 AI 模型&#xff0c;可能会发现本演练很有帮助。 在 Internet 上找到的 3D 模型&#xff…

Netty核心组件模块(二)

1.EventLoop组件 1.1.ChannelHandlerContext 1>.保存Channel相关的所有上下文信息,同时关联一个ChannelHandler对象; ChannelHandlerContext底层真实的类型为:DefaultChannelHandlerContext! 2>.即ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时…

GEE遥感云大数据林业应用典型案例实践及GPT模型应用

目录 一 平台及基础开发平台 二 GEE基础知识与ChatGPT等AI模型交互 三 重要知识点微型案例串讲与GPT模型交互演示 四 典型案例综合演练 更多推荐 聚焦目前遥感应用最热门领域之一的林业&#xff0c;重点结合典型应用案例综合展示GEE云平台的使用技巧和强大功能&#xff0c…

Spring Aop以及SpringBoot统一功能的处理

一.SpringAop 1.SpringAop是一种思想&#xff0c;指的是对使用比较多的功能进行统一处理&#xff0c;比如我们在写博客系统项目&#xff0c;当我们在登录博客列表页和博客详情页以及博客编辑页的时候的时候&#xff0c;都需要写代码进行登录验证&#xff0c;这时候代码就比较繁…

linux内核篇-内存管理(虚拟内存和物理内存、进程虚拟内存布局、内存映射)

主要包括虚拟内存和物理内存、进程内存空间、用户态和内核态的内存映射。 分段机制 分段机制比较符合逻辑&#xff0c;比如可以把程序分成代码段、全局变量段、堆栈段等。 分段的虚拟地址主要包含段选择因子和段内偏移。段选择子就保存在段寄存器中&#xff0c;段选择子中有…

【资料分享】低速数字输入电路

1、方案设计&#xff1a;单通道、单向、反相器 该电路采用单通道&#xff0c;单向光耦&#xff0c;只支持漏型输入&#xff0c;电路的输入端压差满足24V DC10%(21.6V DC-26.4V DC)&#xff0c;输出端电压在0~3.3V范围摆动。 1.1关键技术规格 1.2具体原理图 1.3电路原理详解 …

数字图像处理-基础

数字图像处理-基础 文章目录 一、闲谈二、人类视觉系统三、光和电磁波谱四、图像感知与获取五、图像取样与量化5.1. 数字图像的表示5.2. 空间和灰度分辨率5.3. 图像内插5.3.1. 最近邻内插5.3.2. 双线性内插5.3.3. 双三次内插 六、像素间的关系6.1. 相邻像素6.2. 邻接性、连通性…

【架构设计】阿里开源架构Cola4.0的项目实践:订单系统

项目介绍 使用SpringBootMybaitsPlusCola&#xff08;整洁面向对象分层架构&#xff09;4.0重构订单功能 项目地址 Gitee&#xff1a;https://gitee.com/charles_ruan/smile-cola Github&#xff1a;https://github.com/charles0719/smile-cola 项目核心API 新增 POST http:…

华为OD机试真题 Java 实现【整理扑克牌】【2023Q1 100分】

一、题目描述 给定一组数字&#xff0c;表示扑克牌的牌面数字&#xff0c;忽略扑克牌的花色&#xff0c;请按如下规则对这一组扑克牌进行整理&#xff1a; 步骤1 对扑克牌进行分组&#xff0c;形成组合牌&#xff0c;规则如下&#xff1a; 当牌面数字相同张数大于等于4时&a…

【FPGA】Verilog:锁存器 Latch | RS Flip-Flop 与 D Flip-Flop 的实现

&#x1f4ad; 写在前面&#xff1a;本章将理解 RS/D 锁存器的概念&#xff0c;了解 RS/D/JK 触发器的概念&#xff0c;使用 Verilog 实现各种锁存器 (Latch) 和翻转器 (Flip-Flop)&#xff0c;并通过 FPGA 验证用 Verilog 的实现。 &#x1f4dc; 本章目录&#xff1a; Ⅰ. …

Java中synchronized的优化

本文介绍为了实现高效并发&#xff0c;虚拟机对 synchronized 做的一系列的锁优化措施 高效并发是从 JDK5 升级到 JDK6 后一项重要的改进项&#xff0c;HotSpot 虚拟机开发团队在 JDK6 这个版本上花费了大量的资源去实现各种锁优化技术&#xff0c;如适应性自旋&#xff08;Ada…

【fly-iot飞凡物联】(6):通过docker镜像使用gitbook启动ActorCloud项目文档,发现是个IOT功能非常丰富的项目,可以继续研究下去。

目录 前言1&#xff0c;关于 ActorCloud 使用手册2&#xff0c;使用docker 构建文档4&#xff0c;或者使用别人的gitbook镜像5&#xff0c;总结 前言 本文的原文连接是: https://blog.csdn.net/freewebsys/article/details/108971807 fly-iot飞凡物联专栏&#xff1a; https://…

含sop的配电网重构(含风光|可多时段拓展)

目录 1 主要内容 2 部分程序 3 下载链接 1 主要内容 之前分享了很多配电网重构的程序&#xff0c;每个程序针对场景限定性比较大&#xff0c;程序初学者修改起来难度较大&#xff0c;本次分享一个基础程序&#xff0c;针对含sop的配电网重构模型&#xff0c;含风电和光伏&…

skywalking安全认证问题

skywalking安全认证 一、问题二、步骤2.1 skywalking-aop配置文件修改2.2 agent配置文件修改 一、问题 在springboot项目使用java-agent接入skywalking时&#xff0c;为保证两者之间的数据安全传输&#xff0c;准备加个安全认证 参考文章&#xff1a; https://www.helloworld…

尝试探索水下目标检测,基于yolov5轻量级系列模型n/s/m开发构建海底生物检测系统

其实&#xff0c;水下目标检测相关的项目早在之前就已经做了几个了&#xff0c;但是没有系统性地对比过&#xff0c;感兴趣的话可以先看下之前的文章&#xff0c;如下&#xff1a; 《基于自建数据集【海底生物检测】使用YOLOv5-v6.1/2版本构建目标检测模型超详细教程》 《基于…

Qt编写视频监控系统73-不同视频流不同类型的判断和解析(http/m3u8/rtsp/rtmp等)

一、前言 这套视频监控系统大概从2018年起步整体框架&#xff0c;一步步积累到现在&#xff0c;中间经历了无数次的各种视频文件、视频流、视频设备的播放测试&#xff0c;比如光视频文件就有mp4/wmv/rmvb/mkv/avi等格式&#xff0c;视频设备有本地USB摄像头、桌面等&#xff…

【k8s】【ELK】【zookeeper+kafka+efak】日志环境部署

1、日志收集基本概念 k8s中pod的路径&#xff1a; containers log: /var/log/containers/*.log Pod log&#xff1a; /var/log/pods docker log: /var/lib/docker/containers/*/*.log如何收集日志 使用 EFKLogstashKafka 1、filebeat读取容器中的日志&#xff0c;然后写入K…

camunda如何发布和调用rest服务接口

一、camunda如何发布rest服务接口 Camunda BPM 平台本身提供了 REST API 接口&#xff0c;可以用于管理和操作 Camunda 平台中的各种资源和数据&#xff0c;如流程定义、流程实例、任务等。因此&#xff0c;我们可以通过编写 Camunda 应用程序的方式&#xff0c;将 Camunda RE…

Ubuntu系统远程桌面安装运行记录

Ubuntu系统远程桌面安装运行记录 分别测试了20.04和22.04两个版本 一、widows远程 参考连接https://blog.csdn.net/qq_50263172/article/details/128465149 安装步骤如下&#xff1a; sudo apt-get install xrdpsudo apt install tightvncserversudo apt-get install xubuntu-…