filter和interceptor

Filter和Interceptor的使用

Filter和Interceptor的不同点

  1. Filter是Servlet的,它主要用于对用户的请求进行预处理,也可以对ServletResponse进行后置处理.
  2. Interceptor拦截器,在AOP中用于某个方法或字段被访问前,进行拦截,然后在之前或者之后做一些拦截操作,比如日志。
  3. 从前两点就可以看出来,Filter和Interceptor的作用时机是不同的,Filter都没有机会进到方法那里,所以如果我们想做一些权限的校验我们可以采用过滤器,如果我们想对方法做一些加强,比如方法前后做一些日志,那我们就可以使用Interceptor拦截器了

Filter实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
public class OpenApiFilter implements Filter {

@Autowired
private RedisCache redisCache;

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.warn("开始 进行 open api filter");
HttpServletRequest request = (HttpServletRequest) servletRequest;
String requestURI = request.getRequestURI();
String xClientId = request.getHeader("x-Client-id");

// 判断是否存在xClientId, 不存在说明不是请求open api 继续下个节点过滤,存在继续校验
if (StringUtils.isNotEmpty(xClientId)) {
// 从redis中获取xClientId对应的open api 接口
String openApiStr = Optional.ofNullable(redisCache.getCacheObject(xClientId))
.map(item -> {
return item.toString();
})
.orElse(null);
if (StringUtils.isNotNull(openApiStr)) {
String[] split = openApiStr.split(";");
List<String> openApiList = Arrays.stream(split).collect(Collectors.toList());
// 判断当前uri是否包含在资源里面 包含通过 不包含不通过
if (openApiList.contains(requestURI)) {
filterChain.doFilter(servletRequest, servletResponse);
}else {
log.warn("IP:{}不存在该open api {}的访问权限", request.getRemoteAddr(), requestURI);
}
}else {
log.warn("IP:{}不存在该open api {}的访问权限, 请检查x-Client-id的正确性", request.getRemoteAddr(), requestURI);
}
}else {
log.warn("IP:{}不存在该open api {}的访问权限", request.getRemoteAddr(), requestURI);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import com.ruoyi.openapi.core.filter.OpenApiFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

import java.util.Arrays;

@Configuration
public class OpenApiFilterConfig {

@Bean(name = "openApiFilter")
public OpenApiFilter getOpenApiFilter() {
return new OpenApiFilter();
}

@Bean
public FilterRegistrationBean registerMyFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
registrationBean.setOrder(1);
// 正常Filter里面是注入不了Bean的,前面也说过Filter更靠前,Filter比Bean先加载
// 是属于Servlet的,他还没有进入spring所以无法使用spring的注入
// 我们可以采用filter配置文件<filter>和Bean的配置文件来解决
// 这里我们使用DelegatingFilterProxy来解决
registrationBean.setFilter(new DelegatingFilterProxy("openApiFilter"));
registrationBean.setUrlPatterns(Arrays.asList("/aiot/openapi/*"));
// 因为我们已经通过DelegatingFilterProxy来实现了所以我们可以把这个filter设置为false,
// 默认为true,如果为true会导致一个请求filter两次
registrationBean.setEnabled(false);
return registrationBean;
}
}

通过测试发现,正常的接口也被过滤了,,,这是为什么?我也不知道,心想着我在setFilter的时候既然用的是DelegatingFilterProxy那我为什么不直接使用DelegatingFilterProxyRegistrationBean呢, 于是改为下面的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import com.ruoyi.openapi.core.filter.OpenApiFilter;
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.DispatcherType;

@Configuration
public class OpenApiFilterConfig {

@Bean(name = "openApiFilter")
public OpenApiFilter getOpenApiFilter() {
return new OpenApiFilter();
}

@Bean
public DelegatingFilterProxyRegistrationBean delegatingFilterProxyRegistrationBean(){
DelegatingFilterProxyRegistrationBean filterProxy = new DelegatingFilterProxyRegistrationBean("openApiFilter");
filterProxy.addUrlPatterns("/aiot/openapi/*");
filterProxy.addInitParameter("targetFilterLifecycle","true");
filterProxy.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico");
filterProxy.setDispatcherTypes(DispatcherType.REQUEST);
return filterProxy;
}
}

这次测试通过,只过滤了我们想要过滤的接口。至于上面那种方式不行还有待探索。参考连接参考

FilterRegistrationBean和DelegatingFilterProxyRegistrationBean区别:

  • FilterRegistrationBean通过onStartup方法直接注册filter。
  • DelegatingFilterProxyRegistrationBean是将DelegatingFilterProxy注册到Servlet3.0+的容器中,同时实现了ApplicationContextAware接口,实例ApplicationContext通过通过传入自定义filter的名称查找对应的bean,并生成相应bean的代理对象。

DelegatingFilterProxy

DelegatingFilterProxy就是一个对于servlet filter的代理,用这个类的好处就是可以通过spring容器来管理servlet filter的生命周期。

还有就是如果filter中需要一些spring容器的实例,可以通过spring直接注入,另外读取一些配置文件这些便利的操作也可以通过spring来配置实现

Spring web在设计的时候考虑到某些功能的实现是通过Filter来拦截进行实现的,如果直接的简单的实现几个Filter好像也不是不可以(平时我们就是这么用的),但是Spring框架最核心的是IOC容器,和Spring框架最好的实现就是将要实现的Filter功能注册到IOC容器的一个Bean,这样就可以和Spring IOC容器进行完美的融合,所以Spring Web设计了DelegatingFilterProxy。

本质上来说DelegatingFilterProxy就是一个Filter,其间接实现了Filter接口,但是在doFilter中其实调用的从Spring 容器中获取到的代理Filter的实现类delegate。

DelegatingFilterProxy原理

  1. DelegatingFilterProxy根据targetBeanName从Spring 容器中获取被注入到Spring 容器的Filter实现类,在DelegatingFilterProxy配置时一般需要配置属性targetBeanName

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    @Override
    protected void initFilterBean() throws ServletException {
    synchronized (this.delegateMonitor) {
    if (this.delegate == null) {
    // If no target bean name specified, use filter name.
    // 当Filter配置时如果没有设置targentBeanName属性,则直接根据Filter名称来查找
    if (this.targetBeanName == null) {
    this.targetBeanName = getFilterName();
    }
    // Fetch Spring root application context and initialize the delegate early,
    // if possible. If the root application context will be started after this
    // filter proxy, we'll have to resort to lazy initialization.
    WebApplicationContext wac = findWebApplicationContext();
    if (wac != null) {
    // 从Spring容器中获取注入的Filter的实现类
    this.delegate = initDelegate(wac);
    }
    }
    }
    }

    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
    //从Spring 容器中获取注入的Filter的实现类
    Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
    if (isTargetFilterLifecycle()) {
    delegate.init(getFilterConfig());
    }
    return delegate;
    }
  2. 在DelegatingFilterProxy的实现方法doFilter中,其实最终调用的是委派的类delegate

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    @Override
    protected void initFilterBean() throws ServletException {
    synchronized (this.delegateMonitor) {
    if (this.delegate == null) {
    // If no target bean name specified, use filter name.
    // 当Filter配置时如果没有设置targentBeanName属性,则直接根据Filter名称来查找
    if (this.targetBeanName == null) {
    this.targetBeanName = getFilterName();
    }
    // Fetch Spring root application context and initialize the delegate early,
    // if possible. If the root application context will be started after this
    // filter proxy, we'll have to resort to lazy initialization.
    WebApplicationContext wac = findWebApplicationContext();
    if (wac != null) {
    // 从Spring容器中获取注入的Filter的实现类
    this.delegate = initDelegate(wac);
    }
    }
    }
    }

    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
    // 从Spring 容器中获取注入的Filter的实现类
    Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
    if (isTargetFilterLifecycle()) {
    delegate.init(getFilterConfig());
    }
    return delegate;
    }

Interceptor的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import com.ruoyi.aiot.webconfig.annotation.DeviceAuth;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

public class DeviceAuthTokenInterceptor implements HandlerInterceptor {

// public static final String NY_AIOT_TOKEN_KEY = "NY_DEVICE_TOKEN";

@Value("${emq.value}")
private String emqToken;

@Value("${emq.header}")
private String header;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果不是映射到方法直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
}

HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
DeviceAuth annotation = method.getAnnotation(DeviceAuth.class);
if (StringUtils.isNull(annotation)){
return true;
}

// 判断token的合法性
String token = request.getHeader(header);
return emqToken.equals(token);
}
}

注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import com.ruoyi.aiot.webconfig.Interceptor.DeviceAuthTokenInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* 设备token认证
* @author DEAN
*/
@Configuration
public class WebDeviceAuthConfigurer implements WebMvcConfigurer {

@Bean
public DeviceAuthTokenInterceptor getDeviceAuthTokenInterceptor(){
return new DeviceAuthTokenInterceptor();
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
// addInterceptor这里如果是通过new DeviceAuthTokenInterceptor()的话
// 我们在DeviceAuthTokenInterceptor 里面注入的Bean都会为null,
// 因为new出来的是不归spring容器管理的
// 所以我们好像DeviceAuthTokenInterceptor里面注入的Bean不为null,这里就要是一个Bean
// 我们就通过@Bean注解的方式获得一个Bean然后add进去就好了 [参考这篇博客](https://cloud.tencent.com/developer/article/1640210)
// 通过new的方式创建的拦截器是没有交给spring进行管理的,没有被spring管理的实例,是无法注入的,所以为null
registry.addInterceptor(getDeviceAuthTokenInterceptor()).addPathPatterns("/aiot/device/state");
}
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!