前言
① 当提到【登陆】二字的时候,我总能在脑子里面想起三个词:【过滤器】、【拦截器】、【Shiro】
② 在我脑中它们三个都有拦截请求的功能,但具体是干啥的我无法说出来。根据费曼学习法,如果我无法说出来,就代表我不会。
③ 接下来学习一下过滤器、拦截器和 Shiro,并记录一下。
一、过滤器(Filter)
(1) 概念
① 官方文档:A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static content), or on the response from a resource, or both.
过滤器是一个对象,它对资源(servlet 或静态资源)的请求或对来自资源的响应执行筛选(过滤)任务,或对两者都执行筛选(过滤)任务。
② 其他同学的博客:当访问服务器的资源时,过滤器可以把请求拦截下来,去执行一些特殊的功能。过滤器页可以把响应拦截下来,控制某些东西是否可以返回给客户端。
③ 提炼一下:Filter 用来拦截、过滤、筛选客户端的请求和服务器的响应。
(2) 过滤器的一般作用
- Authentication Filters 身份验证(登陆)
- Logging and Auditing Filters 日志和审核
- Image conversion Filters 图片转换
- Data compression Filters 数据压缩
- Encryption Filters 加密过滤
- Filters that trigger resource access events 触发资源访问事件的过滤器
(3) 在代码中使用
在 SpringBoot 项目中使用 Filter 的时候,在 Filter 类上要加上 @Component 注解,否则没有效果。【迷糊点】
下面通过 @WebFilter 的方式使用 Filter,暂时不知道 xml 配置文件的方式在 SpringBoot 项目中如何使用。
@WebFilter("/*") // 拦截全部的请求
// SpringBoot 项目中使用 Filter 的话,@Component 注解是必要的
@Component
public class AppleFilter implements Filter {
/**
* 客户端的每次请求都会经过 Filter 的 doFilter 方法,从而实现过滤功能
* ① chain.doFilter 前的代码是对【请求】执行过滤功能
* ② chain.doFilter 后的代码是对【响应】执行过滤功能
*/
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain filterChain)
throws IOException, ServletException {
System.out.println("① chain.doFilter 前的代码是对【请求】执行过滤功能");
filterChain.doFilter(request, response);
System.out.println("② chain.doFilter 后的代码是对【响应】执行过滤功能");
}
}
@RestController
@RequestMapping("/apples")
public class AppleController {
@GetMapping("/apple1")
@ResponseBody
public String apple1() {
System.out.println("AppleController - apple1");
return "AppleController - apple1";
}
}
执行结果:
① chain.doFilter 前的代码是对【请求】执行过滤功能
AppleController - apple1
② chain.doFilter 后的代码是对【响应】执行过滤功能
Filter 是一个接口,Filter 中有三个方法:init()、doFilter()、destroy()
其中,init() 和 destroy() 是默认(default)方法,无需实现(implements)Filter 的类必须实现这两个方法。doFilter 不是默认方法,实现(implements)Filter 接口的类必须实现 doFilter 方法。
init() 方法被调用:called by the web container to indicate to a filter that it is being placed into service.
Filter 的 init 方法由 Web 容器调用,用于指示某个 Filter 被应用于到了服务之中。
Filter 的 init 方法在启动 SpringBoot 项目(运行 main 方法)的过程中被调用。
doFilter() 方法被调用:called by the container each time a request/response pair is passed through the chain due to a client request for a resource at the end of the chain.
destroy() 方法被调用:called by the web container to indicate to a filter that it is being taken out of service.
destroy() 方法由 Web 容器调用,用于指示某个 Filter 已经从服务中停止了。
下面的代码是我第一次学习 Filter 的时候写的,现在来重读一下。
该代码作用:通过拦截器进行登录验证。
① chain.doFilter 之前的代码是对【请求】进行过滤,chain.doFilter 之后的代码是对【响应】进行过滤
② 在下面的代码中,先把 ServletRequest 类型的 req 和 ServletResponse 类型的 resp 转换为 HttpServletRequest 的类型的 request 和 HttpServletResponse 类型的 response。便于对客户端的请求进行筛选和对响应进行处理。
③ HttpServletRequest 的类型的 request 的 getRequestURI 方法可以获取到客户端请求的部分路径(不包含协议、域名、端口号)的字符串,可通过客户端路径进行筛选,判断哪些路径需要进行登录,哪些路径需要直接通过(不用登录)。
④ 调用 chain.doFilter 方法后,才可进入下一链条调用。
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 需要通过 HttpServletRequest 类型的 request 拿到 Session
HttpServletRequest request = ((HttpServletRequest) req);
// 需要通过HttpServletResponse类型的response重定向
HttpServletResponse response = (HttpServletResponse) resp;
String uri = request.getRequestURI();
if (uri.contains("/asset/")) { // 资源文件优先放开
chain.doFilter(req, resp);
} else if (uri.contains("/admin") || uri.contains("/remove") || uri.contains("save")) { // 需要进行过滤的
Object user = request.getSession().getAttribute("user");
if (user != null) { // 登录成功过
chain.doFilter(req, resp);
} else { // 没有登录成功过
response.sendRedirect(request.getContextPath() + "/page/login.jsp");
}
} else { // (login.jsp、captcha)
System.out.println(uri);
chain.doFilter(req, resp);
}
}
(4) Filter 的优先级
当有多个 Filter 的时候,哪个 Filter 先执行?
① 通过注解方式使用 Filter:按照 Filter 的字母顺序,小的哪个 Filter 先执行。
如:CharsetFilter 和 LoginFilter,CharSetFilter 的(C)比 LoginFilter 的(L)小,所以 CharSetFilter 先执行。
② 通过 xml 配置文件使用 Filter:先配置哪个 Filter 就先执行哪个 Filter。
下面是测试代码:
@Component
@WebFilter("/*")
public class CharsetFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("CharsetFilter");
chain.doFilter(request, response);
}
}
@Component
@WebFilter("/*")
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("LoginFilter");
chain.doFilter(request, response);
}
}
@RestController
@RequestMapping("/filterOrders")
public class FilterOrderController {
@GetMapping("/filterOrder1")
@ResponseBody
public String filterOrder1() {
System.out.println("FilterOrderController - filterOrder1");
return "FilterOrderController - filterOrder1";
}
}
执行结果:
CharsetFilter
LoginFilter
FilterOrderController - filterOrder1
(5) Filter 的生命周期方法
public class LoginFilter implements Filter {
// 项目从 Web 容器中取消部署时调用(当 Filter 从 Web 容器中移除时调用)
public void destroy() {
// 一般做资源的回收操作
}
public void doFilter() {
}
// 项目部署到 Web 容器时调用(当 Filter 被加载到 Web 容器中)
public void init() {
// 一般做一些资源的一次性加载、初始化
}
}
(6) @WebFilter 的 dispatchTypes 属性
- REQUEST:只拦截客户端直接发送的请求【默认值】
FORWARD:只拦截客户端转发的请求
@WebFilter(value = "/*", dispatcherTypes = DispatcherType.FORWARD)
(7) 其他点赞高的创作者的文章
二、SpringBoot 项目中配置 SpringMVC
- 默认情况下,SpringBoot 已经内置了很多 SpringMVC 的常用配置
- 若想在 SpringBoot 的默认配置的基础上增加自己的配置,可使用 WebMvcConfigurer 接口
- 在 SpringBoot 项目中使用 SpringMVC 的拦截器也需要配置一下(如下面的代码所示)
/**
* 在 SpringBoot 对 SpringMVC 默认配置的基础上增加自己的配置
*/
@Configuration
//@EnableWebMvc // 完全自定义 SpringMVC 的配置
public class SpringMVCConfig implements WebMvcConfigurer {
/**
* 配置拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
}
- 若想完全自定义 SpringMVC 的配置,可使用 @EnableWebMvc 注解
三、拦截器(Interceptor)
在 SpringMVC 框架中,拦截器是 Java 面试中的常客。
(1) 简介
拦截器(Interceptor)的功能与过滤器(Filter)类似,但有本质的区别。
① 过滤器(Filter)
- 是 Servlet 规范的一部分
- 能过滤任意请求,如 html、js、jsp
- 请求抵达 Servlet 之前,响应抵达客户端之前都可过滤
- 常用于:编码设置、登录校验等
② 拦截器(Interceptor)
- 是 SpringMVC 的一部分
- 只能拦截 DispatcherServlet 能拦截到的内容,一般用来拦截 controller
- 常用于:抽取 controller 的公共代码
除了 jsp,DispatcherServlet 都可以拦截。
(2) 使用拦截器
① 使用
- 创建一个拦截器类(QYInterceptor)实现 HandlerInterceptor 接口
- 创建一个 SpringMVC 配置类(QYWebMvcConfig),实现 WebMvcConfigurer 接口。实现该接口的 addInterceptor 方法
/**
* SpringMVC 的拦截器
*/
public class QYInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("QYInterceptor - preHandle - " + request.getRequestURL());
// 必须返回 true 才会进入下一个步骤
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler, // 通过 handler 可得知是哪个控制器的哪个方法
ModelAndView modelAndView) throws Exception {
System.out.println("QYInterceptor - postHandle - " + handler);
}
/**
* 请求响应结束后调用 afterCompletion
* 请求结果返回给客户端后调用 afterCompletion
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// 下面这种代码执行完之后 afterCompletion 方法被调用
// response.sendRedirect("");
// request.getRequestDispatcher("").forward(request, response);
// response.getWriter().write("");
System.out.println("QYInterceptor - afterCompletion");
}
}
/**
* 在 SpringBoot 对 SpringMVC 默认配置的基础上增加自己的配置
*/
@Configuration
//@EnableWebMvc // 完全自定义 SpringMVC 的配置
public class QYMVCConfig implements WebMvcConfigurer {
/**
* 配置拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
String[] pathPatterns = new String[]{"/sysUsers/login", "/asset/**", "/**/*.html"};
registry.addInterceptor(new QYInterceptor())
.addPathPatterns("/**") // 拦截全部路径
.excludePathPatterns(pathPatterns);
}
}
执行结果:
- HandlerInterceptor 接口中方法的都是默认方法(在接口中有该方法的默认实现)
- HandlerInterceptor 接口中有 3 个默认方法,分别是:preHandle、postHandle、afterCompletion
② 三个默认方法的解析:
- preHandle:在 controller 的处理方法之前调用(该方法返回值为 true 才执行 controller 里面的内容)
-- 通常在 preHandle 中进行初始化、请求预处理(可实现登录验证)
-- preHandle 返回 true 才会执行后面的调用。若返回 false,不会调用 controller 处理方法、postHandle 和 afterCompletion
-- 当有多个拦截器时,preHandle 按照正序执行
- postHandle:在 controller 的处理方法之后,DispatcherServlet 进行视图渲染之前调用
-- 可在 postHandle 中进行请求后续加工处理操作
-- 当有多个拦截器时,postHandle 按照逆序执行
- afterCompletion:在 DispatcherServlet 进行视图渲染之后调用
-- 一般在这里进行资源回收操作
-- 当有多个拦截器时,afterCompletion按照逆序执行
四、Shiro
......