过滤器
简介
什么是过滤器(Filter)
- Filter表示过滤器,是JavaWeb三大组件(Servlet,Filter,Listener)之一
- 过滤器可以把对资源的请求拦截下来,从而实现设置好的特殊功能
- 使用了过滤器之后,想要访问Web服务器上的资源,需要先经过过滤器,过滤器处理完毕之后,才可以访问对应的资源。
- 过滤器一般完成一些通用操作,列如登录校验,统一编码处理,敏感字符处理等
Filter入门程序
- 定义:定义一个类,实现Filter接口,并重写其所有方法
- 配置:Filter类上加上@WebFilter注解,配置拦截资源的路径,引导类上加@ServletComponentScan开启Servlet组件支持
1). 定义过滤器
public class DemoFilter implements Filter {
//初始化方法, web服务器启动, 创建Filter实例时调用, 只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init ...");
}
//拦截到请求时,调用该方法,可以调用多次
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
System.out.println("拦截到了请求...");
}
//销毁方法, web服务器关闭时调用, 只调用一次
public void destroy() {
System.out.println("destroy ... ");
}
}
init方法:过滤器的初始化方法。在web服务器启动的时候会自动的创建Filter过滤器对象,在创建过滤器对象的时候会自动调用init初始化方法,这个方法只会被调用一次。
doFilter方法:这个方法是在每一次拦截到请求之后都会被调用,所以这个方法是会被调用多次的,每拦截到一次请求就会调用一次doFilter()方法。
destroy方法: 是销毁的方法。当我们关闭服务器的时候,它会自动的调用销毁方法destroy,而这个销毁方法也只会被调用一次。
2). 配置Filter
在定义完Filter之后,Filter其实并不会生效,还需要完成Filter的配置,Filter的配置非常简单,只需要在Filter类上添加一个注解:@WebFilter
,并指定属性urlPatterns
,通过这个属性指定过滤器要拦截哪些请求
@WebFilter(urlPatterns = "/*") //配置过滤器要拦截的请求路径( /* 表示拦截浏览器的所有请求 )
public class DemoFilter implements Filter {
//初始化方法, web服务器启动, 创建Filter实例时调用, 只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init ...");
}
//拦截到请求时,调用该方法,可以调用多次
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
System.out.println("拦截到了请求...");
}
//销毁方法, web服务器关闭时调用, 只调用一次
public void destroy() {
System.out.println("destroy ... ");
}
}
当我们在Filter类上面加了@WebFilter注解之后,接下来我们还需要在启动类上面加上一个注解@ServletComponentScan,通过这个@ServletComponentScan注解来开启SpringBoot项目对于Servlet组件的支持。
@ServletComponentScan //开启对Servlet组件的支持
@SpringBootApplication
public class TliasManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TliasManagementApplication.class, args);
}
}
注意:在过滤器中,如果不执行放行操作,将无法访问后面的资源
过滤器执行流程
在过滤器中,拦截到请求之后,如果希望继续访问后面的Web资源,就要执行放行操作,放行就是调用FilerChain对象当中的doFilter()方法,调用这个方法之前编写的代码就属于放行后的逻辑
@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
@Override //初始化方法, 只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init 初始化方法执行了");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("DemoFilter 放行前逻辑.....");
//放行请求
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("DemoFilter 放行后逻辑.....");
}
@Override //销毁方法, 只调用一次
public void destroy() {
System.out.println("destroy 销毁方法执行了");
}
}
拦截路径
我们来了解过滤器的拦截路径,Filter可以根据需求,配置不同的拦截资源路径:
拦截路径 | urlPatterns值 | 含义 |
---|---|---|
拦截具体路径 | /login | 只有访问 /login 路径时,才会被拦截 |
目录拦截 | /emps/* | 访问/emps下的所有资源,都会被拦截 |
拦截所有 | /* | 访问所有资源,都会被拦截 |
拦截器链
过滤器链指的就是在一个Web应用程序当中,可以配置多个过滤器,多个过滤器就形成了一个过滤器链
--> (( -.-
比如我们在web服务器中,定义了两个过滤器,这两个过滤器就形成了一个过滤器链
这个链上的过滤器会在执行的时候会一个一个执行(1后面2),直到执行到最后一个过滤器放行之后,才会访问对应的web资源。而后端响应数据的时候过滤器放行的顺序是反的(2然后1)
登录校验-Filter
我们接下来来看Filter过滤器在登录中如何校验
我们先来回顾一下登录校验的基本流程
-
要进入到后台管理系统,我们必须先完成登录操作,此时就需要访问登录接口login
-
登录成功之后,我们会在服务端生成一个JWT令牌,并且把JWT令牌返回给前端,前端会将JWT令牌存储下来
-
在后续的每一次请求当中,都会将JWT令牌携带到服务器,请求到达服务器之后,要想去访问对应的业务功能,此时我们必须要先检验令牌的有效性
-
对应校验令牌的这一块操作,我们使用登录校验的过滤器,在过滤器当中来校验令牌的有效性,如果令牌是无效的,就响应了一个错误的信息,也不会再去放行访问对应的资源,如果令牌存在,并且它是有效的,此时就会放行去访问对应的web资源,执行相应的业务操作
-
思考
1.所有的请求,拦截到了之后,都需要校验令牌吗?
答:登录请求例外
2.拦截到请求之后,什么情况下才可以放行,执行业务操作?
有令牌,且令牌校验通过(合法);否则都返回未登录错误结果
基于业务,我们可以分析出具体的操作步骤:
- 获取请求url
- 判断请求url中是否包含login,如果包含,说明是登录操作,放行
- 获取请求头中的令牌
- 判断令牌是否存在,如果不存在,响应401
- 解析token,如果解析失败,响应401
- 放行
代码实现
下列是接口手册
-
基本信息
请求路径:/login 请求方式:POST 接口描述:该接口用于员工登录Tlias智能学习辅助系统,登录完毕后,系统下发JWT令牌。
-
请求参数
参数格式:application/json
参数说明:
名称 类型 是否必须 备注 username string 必须 用户名 password string 必须 密码 请求数据样例:
{ "username": "jinyong", "password": "123456" }
-
响应数据
参数格式:application/json
参数说明:
名称 类型 是否必须 备注 code number 必须 响应码, 1 成功 ; 0 失败 msg string 非必须 提示信息 data object 必须 返回的数据 |- id number 必须 员工ID |- username string 必须 用户名 |- name string 必须 姓名 |- token string 必须 令牌 响应数据样例:
{ "code": 1, "msg": "success", "data": { "id": 2, "username": "songjiang", "name": "宋江", "token": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MiwidXNlcm5hbWUiOiJzb25namlhbmciLCJleHAiOjE2OTg3MDE3NjJ9.w06EkRXTep6SrvMns3w5RKe79nxauDe7fdMhBLK-MKY" } }
登录校验过滤器:TokenFilter
/**
* 令牌校验过滤器
*/
@Slf4j
@WebFilter(urlPatterns = "/*")
public class TokenFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//1. 获取请求url。
String url = request.getRequestURL().toString();
//2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行。
if(url.contains("login")){ //登录请求
log.info("登录请求 , 直接放行");
chain.doFilter(request, response);
return;
}
//3. 获取请求头中的令牌(token)。
String jwt = request.getHeader("token");
//4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)。
if(!StringUtils.hasLength(jwt)){ //jwt为空
log.info("获取到jwt令牌为空, 返回错误结果");
response.setStatus(HttpStatus.SC_UNAUTHORIZED);
return;
}
//5. 解析token,如果解析失败,返回错误结果(未登录)。
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
e.printStackTrace();
log.info("解析令牌失败, 返回错误结果");
response.setStatus(HttpStatus.SC_UNAUTHORIZED);
return;
}
//6. 放行。
log.info("令牌合法, 放行");
chain.doFilter(request , response);
* }
}
拦截器
介绍
什么是拦截器?
- 是一种动态拦截方法调用的机制,类似于过滤器
- 拦截器是Spring框架中提供的,用来动态拦截控制器方法的执行
拦截器的作用:
拦截请求,再指定方法调用前后,根据业务需要执行预先设定的代码
在拦截器中,我们通常也是做一些通用性的操作,比如我们可以通过拦截器来拦截前端发起的请求,将登录校验的逻辑全部编写在拦截器中,在校验过程中,如果发现用户登录了,就可以直接放行,去访问Spring当中的资源
步骤
- 定义拦截器
- 注册配置拦截器
自定义拦截器
实现HandlerInterceptor接口,并重写其所有方法
//自定义拦截器
@Component
public class DemoInterceptor implements HandlerInterceptor {
//目标资源方法执行前执行。 返回true:放行 返回false:不放行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle .... ");
return true; //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 .... ");
}
}
preHandle方法:目标资源方法执行前执行,
返回true:放行 返回false: 不放行
postHandle方法:目标资源方法执行后执行
afterCompletion方法:视图渲染完毕后执行,最后执行
注册配置拦截器
创建一个配置类 WebConfig
, 实现 WebMvcConfigurer
接口,并重写 addInterceptors
方法
@Configuration
public class WebConfig implements WebMvcConfigurer {
//自定义的拦截器对象
@Autowired
private DemoInterceptor demoInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(demoInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
}
}
Interceptor详解
拦截路径
在注册配置拦截器的时候,我们要指定拦截器的拦截路径,通过addPathPatterns("要拦截路径")方法,就可以指定要拦截的哪些资源
在配置拦截器时,不仅可以指定要拦截哪些资源,还可以指定不拦截哪些资源,只需要调用excludePathPatterns(不拦截路径)
方法,指定哪些资源不需要拦截
@Configuration
public class WebConfig implements WebMvcConfigurer {
//拦截器对象
@Autowired
private DemoInterceptor demoInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(demoInterceptor)
.addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
.excludePathPatterns("/login");//设置不拦截的请求路径
}}
在拦截器中除了可以设置/**
拦截所有资源外,还有一些常见拦截路径设置:
拦截路径 | 含义 | 举例 |
---|---|---|
/* | 一级路径 | 能匹配/depts,/emps,/login,不能匹配 /depts/1 |
/** | 任意级路径 | 能匹配/depts,/depts/1,/depts/1/2 |
/depts/* | /depts下的一级路径 | 能匹配/depts/1,不能匹配/depts/1/2,/depts |
/depts/** | /depts下的任意级路径 | 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1 |
执行流程
我们来介绍一些拦截器的执行流程,通过执行流程,我们就能够清晰的知道过滤器和拦截器的执行时机
- 当我们打开浏览器访问web应用时,此时我们所定义的过滤器会拦截这次请求,然后会先执行放行前的逻辑,然后再执行放行操作。访问我们所定义的controller当中的接口方法
- Tomcat并不识别所编写的Controller程序,但是它识别Servlet程序,所以在Spring的Web环境中提供了一个非常核心的Servlet:DispatcherServlet(前端控制器),所有请求都会先进行到DispatcherServlet,再将请求转给Controller
- 我们定义了拦截器后,会在执行Controller的方法之前,请求被拦截器拦截住,执行
preHandle()
方法,这个方法执行完成后需要返回一个布尔类型的值,如果返回true,就表示放行本次操作,才会继续访问controller中的方法;日光返回false,则不会放行 - 在controller中的方法执行之后,再回国了执行
postHandle()
这个方法以及agterCompletion()
方法,然后再返回给DipatcherServket,最终再执行过滤器当中放行后的这一部分逻辑的逻辑,执行完毕之后,最终给浏览器响应数据
拦截器和过滤器的区别
- 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。
- 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。
登录校验-Interceptor
通过拦截器来完成实际业务当中的登录校验功能
代码实现
1). Token校验拦截器
@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1. 获取请求url。
String url = request.getRequestURL().toString();
//2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行。
if(url.contains("login")){ //登录请求
log.info("登录请求 , 直接放行");
return true;
}
//3. 获取请求头中的令牌(token)。
String jwt = request.getHeader("token");
//4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)。
if(!StringUtils.hasLength(jwt)){ //jwt为空
log.info("获取到jwt令牌为空, 返回错误结果");
response.setStatus(HttpStatus.SC_UNAUTHORIZED);
return false;
}
//5. 解析token,如果解析失败,返回错误结果(未登录)。
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
e.printStackTrace();
log.info("解析令牌失败, 返回错误结果");
response.setStatus(HttpStatus.SC_UNAUTHORIZED);
return false;
}
//6. 放行。
log.info("令牌合法, 放行");
return true;
}
}
2). 配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
//拦截器对象
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
标签:Filter,拦截器,请求,---,过滤器,拦截,public
From: https://www.cnblogs.com/yifan0820/p/17924644.html