SpringBootWeb 篇-深入了解 Filter 过滤器与 Interceptor 拦截器(实现登录校验 Filter + Interceptor + JWT 令牌)

avatar
作者
筋斗云
阅读量:4

🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍

文章目录

        1.0 Filter 过滤器

        2.0 Filter 过滤器的生成

        2.1 Filter 过滤器链的执行流程

        2.2 实现登录校验(JWT 令牌 + Filter 过滤器)

        3.0 Interceptor 拦截器

        3.1 Interceptor 拦截器的生成

        3.2 实现登录校验(JWT 令牌 + Interceptor 拦截器)

        4.0 Filter 过滤器与 Interceptor 拦截器的联系


        1.0 Filter 过滤器

        Filter 过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。过滤器可以把资源的请求拦截下来,从而实现一些特殊的功能。过滤器一般完成一些通用的操作。比如:登录校验,统一编码处理、敏感字符处理。

        2.0 Filter 过滤器的生成

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

实现 Filter 接口:

        选择:javax.servlet 的包。 

重写的方法:

import javax.servlet.*; import java.io.IOException;  public class demo1 implements Filter {      @Override     public void init(FilterConfig filterConfig) throws ServletException {         Filter.super.init(filterConfig);     }      @Override     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {              }      @Override     public void destroy() {         Filter.super.destroy();     } }

        重写的方法有三个:

        init(FilterConfig filterConfig):初始化方法,Web 服务器启动,创建 Filter 时调用,只调用一次。该方法有默认实现,可以选择不重写该方法。

        destroy():销毁方法,服务器关闭时调用,只调用一次。该方法有默认实现,可以选择不重写该方法。

        doFilter(ServletRequest request,ServletResponse response,FilterChain chain):拦截请求时,调用该方法,可调用多次。如果满足条件,可以调用 chain.doFilter(requset,response) 方法进行放行操作。该方法必须要重写。

        2)配置 Filter:Filter 类加上 @WebFilter 注解,配置拦截资源的路径

        @WebFilter 注解中的属性 urlPatterns 指定拦截的请求路径。
 

        配置不同的拦截路径,比如:

        "/login":只有访问 /login 路径时,才会被拦截。

        "/emps/*":访问 /emps 下所有资源,都会被拦截。

        "/*":访问所有资源都会被拦截。

        3)启动类上加上 @ServletComponentScan 开启 Servlet 组件支持。

代码演示:

import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException;  @WebFilter(urlPatterns = "/*") public class demo1 implements Filter {      @Override     public void init(FilterConfig filterConfig) throws ServletException {         System.out.println("拦截之前执行该方法,只能执行一次!!!!!");     }      @Override     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {         //放行之前执行         System.out.println("拦截请求之前,可执行多次!!!!!");          //放行         filterChain.doFilter(servletRequest,servletResponse);          //放行之后执行         System.out.println("拦截请求之后,可执行多次!!!!!");     }      @Override     public void destroy() {         System.out.println("拦截之后执行该方法,只能执行一次!!!");     } }

        在程序启动后,就会立马执行 init(FilterConfig filterConfig) 初始化的方法,且只执行一次。

        当客户端发送请求时,Filter 过滤器就会进行拦截。

        当程序结束,断开连接后,执行 destroy() 方法。

        2.1 Filter 过滤器链的执行流程

        过滤器链,也就是说有多个 Filter 过滤器进行连接。

只有一个 Filter 过滤器的情况:

        当客户端发送请求时,Filter 就会进行拦截,调用 doFilter() 方法进行拦截,如果满足条件,那么就会调用 filterChain.doFilter() 方法放行,让其去访问其他资源。访问完资源后,还会回到 Filter 拦截器中,执行拦截后的代码,最后响应给客户端。

多个 Filter 过滤器的情况:

        Filter 过滤器会按照过滤器类名(字符串)的自然排序。当客户端发送请求时,来到第一个 Filter1 过滤器,先执行前置代码,再执行 chain.doFilter() 方法,接着到下一个 Filter2 过滤器的前置代码,再执行 chain.doFilter() 方法,再去访问目标资源。再回到第二个 Filter2 过滤器,执行后置代码,继续执行 Filter1 过滤器的后置代码。最后响应回到服务端。

如图: 

代码演示:

demo1 代码:

import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException;  @WebFilter(urlPatterns = "/*") public class demo1 implements Filter {      @Override     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {         //放行之前执行         System.out.println("demo1 拦截请求之前,可执行多次!!!!!");          //放行         filterChain.doFilter(servletRequest,servletResponse);          //放行之后执行         System.out.println("demo1 拦截请求之后,可执行多次!!!!!");     }  }

demo2 代码:

import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException;  @WebFilter(urlPatterns = "/*") public class demo2 implements Filter {     @Override     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {         //放行之前执行         System.out.println("demo2 拦截请求之前,可执行多次!!!!!");          //放行         filterChain.doFilter(servletRequest,servletResponse);          //放行之后执行         System.out.println("demo2 拦截请求之后,可执行多次!!!!!");     }  }

运行结果:

        2.2 实现登录校验(JWT 令牌 + Filter 过滤器)

        实现思路:当客户端访问登录的路径,此时执行放行操作,来获取 JWT 令牌。当客户端获取到令牌,之后的每一次请求的请求头中假设 token 字段携带 JWT 令牌来访问服务端资源。Filter 过滤器进行拦截客户端发送过来的请求,进行对 JWT 令牌校验。若正确就可以放行,若不正确返回一个 Result.error() 的 json 格式的结果到客户端。

类对象格式转化为 json 格式方法:

        引入阿里巴巴 fastJSON 工具包,转换成 JSON 格式。

添加依赖:

    <!--fastJSON-->     <dependency>       <groupId>com.alibaba</groupId>       <artifactId>fastjson</artifactId>       <version>1.2.76</version>     </dependency>

         调用 JSONObject.toJSONString(result) 方法,将类对象的形式转化成 JSON 格式。

        1)获取 JWT 令牌

        当请求的路径为 "/login" ,那么服务端给客户端响应一个 JWT 令牌。那么 Filter 过滤器在拦截请求的时候,先判断请求路径是否包含 "/login" 字符串,如果是,直接放行。

代码演示:

        //获取url         String url = request.getRequestURI();          //判断该url是否是登录请求         if (url.contains("login")){             //如果是的话,直接放行             filterChain.doFilter(servletRequest,servletResponse);             return;         }

        2)对 JWT 令牌校验

        当请求路径不存在 "/login",那么就需要判断请求体中是否携带 token 字段,如果没有 token 字段,就返回错误信息;如果有 token 字段,获取到 JWT 令牌后需要进行校验。通过就可以放行,不通过同样返回错误信息。

代码实现:

import com.alibaba.fastjson.JSONObject; import org.example.Pojo.Result; import org.example.Utilities.JWT;  import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;  @WebFilter(urlPatterns = "/*") public class filter implements Filter {     @Override     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {         HttpServletRequest request = (HttpServletRequest) servletRequest;         HttpServletResponse response = (HttpServletResponse) servletResponse;          //获取url         String url = request.getRequestURI();          //判断该url是否是登录请求         if (url.contains("login")){             //如果是的话,直接放行             filterChain.doFilter(servletRequest,servletResponse);             return;         }          //获取令牌         String jwt = request.getHeader("token");          //判断是否存在令牌         if (jwt == null || jwt.equals("")){             //如果令牌不存在的话,直接返回错误信息             Result result = Result.error("error");             //需要将对象格式转换成json格式             String ret = JSONObject.toJSONString(result);             response.getWriter().write(ret);             return;         }          //对令牌进行校验         try {             JWT.parse(jwt);         }catch (Exception e){             Result result = Result.error("error");             String s = JSONObject.toJSONString(result);             response.getWriter().write(s);             return;         }         //合法放行         filterChain.doFilter(servletRequest,servletResponse);     } }

生成 JWT 令牌和校验 JWT 令牌代码:

import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm;  import java.util.Date; import java.util.Map;  public class JWT {      private static final String password = "test";     private static final int time = 3600 * 1000;     //生成令牌     public static String getJWT(Map<String,Object> map){         return Jwts.builder().signWith(SignatureAlgorithm.HS256,password)                 .addClaims(map).setExpiration(new Date(System.currentTimeMillis() + time))                 .compact();     }       //解析令牌     public static Claims parse(String s){         return Jwts.parser().setSigningKey(password).parseClaimsJws(s).getBody();     }  }

LoginController 类:

import java.util.HashMap; import java.util.Map;  @RestController public class LoginController {      @Autowired     private EmpService empService;      @PostMapping("/login")     public Result login(@RequestBody Emp emp){         Emp e = empService.getUserAndPassword(emp);         if (e != null){             Map<String,Object> claims = new HashMap<>();             claims.put("姓名",e.getName());             claims.put("用户名",e.getUsename());             String s = JWT.getJWT(claims);             return Result.success(s);         }         return Result.error("用户登录失败!!!");     }  }

        3.0 Interceptor 拦截器

        是一种动态拦截方法调用的机制,类似于过滤器。Sring 框架中提供的,用来动态拦截控制器方法的执行。在拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。

        3.1 Interceptor 拦截器的生成

        1)定义拦截器,实现 HandlerInterceptor 接口,并重写其所有方法。在拦截器加 @Component 注解,成为 IOC 容器的 Bean 对象。

重写的方法:

        preHandle(HttpServletRequest request, HttpServletResponse response, Object handler):目标资源方法执行前执行,返回 true 表示放行;返回 flas 表示不放行。

        postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView):目标资源方法执行后执行。

        afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex):视图渲染完毕后执行,最后执行。

代码演示:

import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;  import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;  @Component public class MyInterceptor implements HandlerInterceptor {     @Override     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {                  System.out.println("拦截之前执行的代码");         //返回 true 表示放行;返回 false 表示不放行         return true;     }      @Override     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {         System.out.println("访问完资源后,执行的方法!!!!!");     }      @Override     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {         System.out.println("最后执行的方法!!!!");     }  }

        2)注册拦截器

        定义完拦截器之后,需要进行配置。在类加上 @Configuration 则成为配置类。

        首先实现 WebMvcConfigurer 接口,重写 addInterceptors(InterceptorRegistry registry) 方法,利用 registry 来调用 addInterceptor(拦截器),再调用 addPathpatterns() 方法来选择需要拦截的路径,excludePathPatterns() 方法来选择不需要拦截的路径。

代码演示:

 import org.example.Interceptor.MyInterceptor; 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 MyConfiguration implements WebMvcConfigurer {      @Autowired     MyInterceptor myInterceptor;      @Override     public void addInterceptors(InterceptorRegistry registry) {         registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/login");     } }

拦截器可以根据需求,配置不同的拦截路径:

        1)"/*":拦截一级路径,比如:能匹配 /depts,不能拦截 /depts/1 。

        2)"/**":拦截任意级路径,比如:能匹配 /depts、/depts/1、/depts/1/2 。

        3)"/depts/*":拦截 /depts 下的一级目录,比如:能匹配 /depts/1,不能匹配 /depts/1/2 ,/depts 。

        4)"depts/**":拦截 /depths 下的任意级路径,比如:能匹配 /depts,/depts/1 。

        3.2 实现登录校验(JWT 令牌 + Interceptor 拦截器)

        实现思路:当客户端访问登录的路径,此时执行放行操作,来获取 JWT 令牌。当客户端获取到令牌,之后的每一次请求的请求头中假设 token 字段携带 JWT 令牌来访问服务端资源。Interceptor 拦截器进行拦截客户端发送过来的请求,进行对 JWT 令牌校验。若正确就可以放行,若不正确返回一个 Result.error() 的 json 格式的结果到客户端。

代码实现:

import com.alibaba.fastjson.JSONObject; import org.example.Pojo.Result; import org.example.Utilities.JWT; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;  import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;  @Component public class MyInterceptor implements HandlerInterceptor {     @Override     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {          System.out.println("Interceptor 拦截之前执行的代码");         //先获取请求路径         String url = request.getRequestURI();          //这里不需要判断是否是/login,因为在注册拦截器之前就已经设置将/login请求不需要拦截了 /*        if (url.contains("login")){             return true;         }*/           //直接来判断是否请求头中有 token 字段         String jwt = request.getHeader("token");          if(jwt == null || jwt.equals("") ){             //直接返回错误信息             Result result = Result.error("error");             String js = JSONObject.toJSONString(result);             response.getWriter().write(js);             return false;         }          //存在 JWT 令牌时,就需要判断是否正确         //如果出现异常,那么令牌不正确         try {             JWT.parse(jwt);         } catch (Exception e) {             Result result = Result.error("error");             String js = JSONObject.toJSONString(result);             response.getWriter().write(js);             return false;         }          //令牌校验正确的,返回 true             return true;     }      @Override     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {         System.out.println("Interceptor 访问完资源后,执行的方法!!!!!");     }      @Override     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {         System.out.println("Interceptor 最后执行的方法!!!!");     }  }

        4.0 Filter 过滤器与 Interceptor 拦截器的联系

        Filter 是 Servlet 规范中的一种对象,它可以在请求被发送到 Servlet 之前或响应被发送回客户端之前对请求和响应进行预处理或后处理。Filter 主要用于实现一些通用的处理逻辑,如日志记录、权限控制、字符编码转换等。

        Interceptor 是 Spring 框架中的一种对象,它也可以在请求处理前后进行操作,但主要是用于对 Controller 方法的调用进行拦截和处理。Interceptor 可以在请求到达 Controller 前对参数进行处理,也可以在 Controller 执行完毕后对返回结果进行处理,通常用于实现一些业务逻辑的拦截和处理。

        两者的联系在于它们都可以在处理请求和响应的过程中进行一些预处理或后处理操作,但 Filter 是基于 Servlet 规范实现的,对所有的请求都会生效,而 Interceptor 是在 Spring 框架中实现的,主要用于对 Controller 方法的拦截和处理。在实际开发中,通常会同时使用 Filter 和 Interceptor 来实现不同层次的请求处理逻辑。

        Filter 过滤器与 Interceptor 拦截器配合使用,请求先会被 Filter 过滤器拦截,先执行 Filter 过滤器的前置代码,再执行 doFilter() 方法。如果可以通过,那么该请求就会来到 Interceptor 拦截器,同样先执行前置代码,如果可以放行,那么就可以访问资源。资源访问完之后,又来到 Interceptor 拦截器,执行 postHandle() 方法和 afterCompletion() 方法。再回到 Filter 过滤器中,执行 doFilter() 方法后续的代码。最后响应给客户端。

执行的顺序:

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!