一、Filter过滤器
前面我们学会了最先进的会话跟踪技术jwt令牌,那么我们要让用户使用某些功能时就要根据jwt令牌来验证用户身份,来决定他是否登陆了、让不让用户访问这个页面(或功能)
但是这样一来,没发一个请求,后端都要执行一次jwt逻辑判断的逻辑,就会很麻烦
那就需要一个工具:【Filter过滤器】,前端发完请求之后卡在中间,执行一次jwt令牌校验,先过他这关再访问
另外我们前面了解过Servlet是什么了,那么Filter其实也就是【Servlet容器】的【过滤器】,也是Servlet新增的一个功能。“广义上” Filter就是Servlet里的一个【子类】,“狭义上” Filter是Servlet里的一个【接口】
完整官方的表述就是:【实现了javax.servlet.Filter接口的类】
那么【javax.servlet.Filter接口】里定义了三个方法:
那么我们需要怎么用Filter?
简单的Filter实现类的大致结构:
Filter结构中间的【doFilter( )】方法详解:
那么我们能够知道一个大致的Filter结构是怎么样,很显然,我们根本不需要了解【第一部分init( )】和【最后一部分destroy( )】,只要知道有这两玩意,记得写(复制粘贴)上去就行
重点是中间的【doFilter( )】方法,该如何执行这个方法进行逻辑操作?
利用Filter实现登录校验
首先大体流程:
注意三个点:
接下来我们来写一下代码:
这里我累了,你们看代码吧,我不想解释了
package com.czm.tliaswebmanagement.filter;
import com.alibaba.fastjson.JSONObject;
import com.czm.tliaswebmanagement.pojo.Result;
import com.czm.tliaswebmanagement.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.annotation.WebFilter;
import javax.servlet.*;
import java.io.IOException;
@WebFilter(urlPatterns = "/*" )
public class DemoFilter implements Filter {
@Autowired
private JwtUtils jwtUtils;
@Override //执行拦截、过滤逻辑操作,【执行多次】
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//【HttpServletRequest】是【ServletRequest】的父类,【HttpServletResponse】是【ServletResponse】的父类
//为什么强转request和response的类型?因为ServletRequest没有【.getRequestURI()】获取【请求url】的功能
//ServletResponse也没有【.getWriter().write("...")】可以直接把内容写进【响应头】的方法
javax.servlet.http.HttpServletRequest req = (javax.servlet.http.HttpServletRequest) request;
javax.servlet.http.HttpServletResponse res = (javax.servlet.http.HttpServletResponse) response;
//1、获取请求url
String url = req.getRequestURI();
//2、判断url中是否含有登录接口(/login),是登录接口(/login)就放行
if(url.contains("login")){
//放行
chain.doFilter(request , response);
return;//然后结束【doFilter】,不再执行下面内容
}
//3、获取【请求头】里的【token】里存着的【jwt令牌】
//HttpServletRequest 的 getHeader("...");可以获取到请求头的某个属性的值
String jwt = req.getHeader("token");
//4、判断令牌是否存在,如果不存在,返回错误结果(未登录)
//【StringUtils.hasLength(字符串)】可以判断有没有字符串
// StringUtils选这个【org.springframework.util】包!!!
if(!StringUtils.hasLength(jwt)){
//为什么不能直接return这个【Result.error()】信息?
//因为那是在controller层,有【@RestController】注解帮我们把【对象】转成【json】给回前端
//但是在现在这是在Filter类里,要我们手动把【对象】转成【json】,再return
Result error = Result.error("未登录");
//调用阿里巴巴的【fastjson】帮忙转
String notLogin = JSONObject.toJSONString(error);
//然后HttpServletResponse 的 getWriter().write("...")可以直接把内容写进【响应头】
res.getWriter().write(notLogin);
return;
}
//5、如果有令牌,那就判断令牌有无有误(错的令牌在【解析jwt】时就会直接报错)
try {
//调用jwtUtils工具【解析jwt】
jwtUtils.parseJWT(jwt);
} catch (Exception e){
e.printStackTrace();
Result error = Result.error("未登录");
//调用阿里巴巴的【fastjson】帮忙转
String notLogin = JSONObject.toJSONString(error);
//然后HttpServletResponse 的 getWriter().write("...")可以直接把内容写进【响应头】
res.getWriter().write(notLogin);
}
//6、如果不是【登录】请求,也不存在【4】【5】的令牌错误,那就说明这个用户令牌无误,给他放行
chain.doFilter(request, response);
}
}
注意三个地方:
二、Interceptor拦截器
Interceptor是区别于Filter的一种【动态】拦截器,基于springMVC技术实现,简单说Filter就是一个傻楞的门卫挡在城门外,然后Interceptor就是在城区里动态拦你路的烦人保安,这里只做简单解释,结尾我会详细讲它两的区别
简单入门(大体上就两步):
(注意,这里跟Filter一样比较绕脑,但是千万别去理解为什么要这么做!!你要做的就是记住我这里的步骤,你只要知道你要按步骤做就行,别记代码!别研究为什么这么做!)
详细讲解:
那为什么说他是动态的呢?看下图
例子:
那么我们现在结合登录校验案例来实现Interceptor的逻辑
超嗨简单,逻辑跟Filter一模一样!!我们只要记住三个区别:
所以你就可以直接复制粘贴Filter那的代码,然后改完这三个地方直接用了:
package com.czm.tliaswebmanagement.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.czm.tliaswebmanagement.pojo.Result;
import com.czm.tliaswebmanagement.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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 LoginCheckInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtils jwtUtils;
@Override//目标资源方法运行前运行,返回true:放行;返回false:不放行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle,执行【目标资源方法】前");
//为什么这里不用强转两个参数的类型?因为request就是【HttpServletRequest】类型啊,response就是【HttpServletResponse】类型啊
//1、获取请求url
String url = request.getRequestURI();
//2、判断url中是否含有登录接口(/login),是登录接口(/login)就放行
if(url.contains("login")){
//放行
return true;
}
//3、获取【请求头】里的【token】里存着的【jwt令牌】
//getHeader("...");可以获取到请求头的某个属性的值
String jwt = request.getHeader("token");
//4、判断令牌是否存在,如果不存在,返回错误结果(未登录)
if(!StringUtils.hasLength(jwt)){
Result error = Result.error("未登录");
//调用阿里巴巴的【fastjson】手动帮忙转json返回值
String notLogin = JSONObject.toJSONString(error);
response.getWriter().write(notLogin);
//不放行
return false;
}
//5、如果有令牌,那就判断令牌有无有误(错的令牌在【解析jwt】时就会直接报错)
try {
//调用jwtUtils工具【解析jwt】
jwtUtils.parseJWT(jwt);
} catch (Exception e){
e.printStackTrace();
Result error = Result.error("未登录");
//调用阿里巴巴的【fastjson】帮忙转
String notLogin = JSONObject.toJSONString(error);
response.getWriter().write(notLogin);
//不放行
return false;
}
//6、如果不是【登录】请求,也不存在【4】【5】的令牌错误,那就说明这个用户令牌无误,给他放行
return true;
}
@Override//目标资源方法运行后运行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle,执行【目标资源方法】后");
}
@Override//视图渲染完毕后运行,最后运行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion,最后执行【视图渲染完毕】后");
}
}
用apifox请求验证一下
三、Filter过滤器与Interceptor拦截器的区别!!!
那么这两玩意到底有什么区别?不都是拦截吗?下面我就以几个重点分析一下:
1、身世不同
Filter是【Servlet】“家族”的人,写Filter类的时候你们也能发现必须要到Servlet的包
而Interceptor是【springMVC】“家族”的人,写Interceptor的配置类的时候就用到springMVC
2、实现逻辑不同
Filter过滤器是基于函数回调
我们写代码时会发现每个Filter类都有三个参数,其中最后一个参数是【FilterChain】类参数,这就是一个接口参数
然后每个类都有一个【doFilter( )】函数,而我们执行放行的操作不就是在【doFilter( )】函数里调用那个【FilterChain】的【doFilter( )】函数吗?写的时候会不会觉得很奇怪,其实这就是在回调这个【FilterChain】父接口的【doFilter( )】函数,说起来可能不明白,看图:
而Interceptor则是基于Java的反射机制(动态代理)实现的
不好解释我也不知道怎么解释,反正就是直接return回一个布尔值,然后在拦截器接口内部调用其他方法,根据return回来的布尔值来处理放不放行,具体源代码内部逻辑我也没研究所以我不会.......
3、拦截的位置、范围不一样
Filter的拦截的位置是【在Servlet容器里面、SpringMVC容器外面】就进行过滤拦截了
那么它的拦截范围也就是对【所有访问、资源】都进行拦截
Interceptor的拦截位置是【在SpringMVC容器里面、Controller接口前面】
那么它的拦截范围也就是【针对Spring MVC的访问】进行拦截
(多看几张图加深一下记忆,图片都是来自别的博主文章摘录)
4、触发时机不同
既然位置不同,那么触发时机肯定就不一样了,Filter在外面阻拦,那肯定先执行它;然后再到Interceptor;结束了Interceptor之后又回到Filter
5、使用范围不同、实现接口不同
Filter过滤器实现的是【 javax.servlet.Filter 】接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用
而Interceptor拦截器是一个【Spring MVC组件】,实现的是【Handler Interceptor】接口,并由【Spring MVC容器】管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中
另外:DispatcherServlet
Spring环境下,Servlet还提供了一个组件【DispatcherServlet】,它是在Spring MVC容器里的最前面的,在Interceptor拦截器前面,那么大致流程就是:Filter过滤器先过滤拦截外部请求访问——>然后放到DispatcherServlet——>DispatcherServlet再往Controller传,但又遇到Interceptor拦截器阻拦——>最后Interceptor放行,请求资源到达Controller层
我最后最后用最通俗易懂的话来解释:
外部请求是一个乘客,买了一张高铁票要上高铁
Filter就是高铁检票的N多个闸机,它就固定在那,你要进去大厅、进站台就得被它拦一次
然后DispatcherServlet就是站台,你终于通过重重难关来到站台,等待高铁到来然后坐到座位
Interceptor就是检票员,你终于准备要找到高铁座位,准备坐下,Interceptor检票员就动态随机的出现在你面前,要检查你的高铁票,发现你是逃票、站票就让你滚一边呆着,是坐票你就坐着
最后到家的时候,你还是原路返回,只不过Interceptor检票员不会查你了,而是直接放行;你回到站台;到了大厅,过了检票闸机;最后回家,让你爸妈获得你这个宝贝儿子/女儿。