Spring Boot 之 Filter

2020-02-26  本文已影响0人  大哥你先走

什么是Filter

Filter是request/response执行过滤任务的对象,资源可以是一个servlet或者静态资源。Filter在doFilter 方法中执行过滤逻辑。每个Filter都有一个FilterConfig对象,可以从FilterConfig对象获取初始化参数和ServletContext引用。

Filter能干什么

Filter工作在客户端和Servlet之间,可以对客户端request以及服务器的response进行处理,基于此Filter可以用于实现以下功能:

定义Filter

定义一个Filter需要javax.servlet.Filter 接口,Filter 有如下三个方法:

第一个Filter

下面定义一个简单的Filter,在init() 方法中初始化Filter的名字,在doFilter 方法中简单的记录Filter被调用,destroy() 简单的记录方法被调用。

FirstFilter:

public class FirstFilter implements Filter {
    private String filterName;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        if (filterConfig != null) {
            this.filterName = filterConfig.getFilterName();
        }
        System.out.println(filterName + " init finished");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println(filterName + " filter request");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println(filterName + " filter response");
    }


    @Override
    public void destroy() {
        System.out.println(filterName + " begin to destroy");
    }
}

注册Filter

在完成Filter的创建后,还需要将Filter注册到Web容器(添加到Filter chain)才能对request/response进行过滤。在Spring Boot中注册Filter非常简单,下面是一个简单注册Filter的样例:

@Configuration
public class FilterConfigure {
    
    @Bean
    public FilterRegistrationBean<Filter> registrationBean() {
        FilterRegistrationBean<Filter> filter = new FilterRegistrationBean<>();
        filter.setFilter(new FirstFilter());
        filter.setName("first filter");
        return filter;
    }
}

其中注解@Configuration@Bean 是必须的。

测试Filter

启动Spring Boot并调用一个测试接口,测试接口可从这里获取。

curl -i http://localhost:5230/api/filter

应用的输出如下:

first filter filter request
first filter filter response

从输出中可以看出Filter完成了初始化,Filter的名字是“first filter”。

Filter在Filter chain中的顺序

如果定义了多个Filter,并期望request/response可以按照设定的顺序依次经过各个Filter(例如:request需要先经过鉴权Filter,鉴权通过后再进入参数校验Filter等),这种情况如何保证Filter的执行顺序呢?在注册Filter的时候可以给每个Filter设置一个数字表示的order,值越小Filter在chain中的位置越靠前。为了严重Filter的执行顺序,我们定义第二个Filter:SecondFilter,源码可从这里获取。然后将两个Filter添加到Web容器中:

@Configuration
public class FilterConfigure {

    @Bean
    public FilterRegistrationBean<Filter> registrationBean() {
        FilterRegistrationBean<Filter> filter = new FilterRegistrationBean<>();
        filter.setFilter(new FirstFilter());
        filter.setName("first filter");
        filter.setOrder(1);
        return filter;
    }

    @Bean
    public FilterRegistrationBean<Filter> registerSecondBean() {
        FilterRegistrationBean<Filter> filter = new FilterRegistrationBean<>();
        filter.setFilter(new SecondFilter());
        filter.setName("second filter");
        filter.setOrder(2);
        return filter;
    }
}

启动Spring Boot并调用一个测试接口,测试接口可从这里获取。

curl -i http://localhost:5230/api/filter

应用的输出如下:

first filter filter request
second filter filter request
second filter filter response
first filter filter response

request依次经过first filter -> second filter,response依次经过second filter - > first filter,Filter的执行顺序满足我们的期望。

样例

通过上面学习的知识,我们实现一个鉴权的Filter。假设客户端访问资源时需要在请求的header中携带两个参数:user和password(生成环境携带账号密码是十分危险的,应该考虑基于Token的鉴权),如果有一个参数没有携带则返回客户端错误的请求(400 Bad Request),如果user和password不匹配或系统不存在用户则返回无权访问 (403 Forbidden)。客户端每成功一次,系统都会记录用户的访问次数。

Filter的实现如下:

@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // filter request
        System.out.println(filterName + " filter request");
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String userName = httpServletRequest.getHeader("user");
        String password = httpServletRequest.getHeader("password");
        boolean userMatch = userService.userMatch(userName, password);
        if (userName == null || password == null) {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.setStatus(HttpStatus.BAD_REQUEST.value());
            return;
        }
        if (!userMatch) {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
            return;
        }

        chain.doFilter(request, response);

        // filter response
        System.out.println(filterName + " filter response");
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        if (httpServletResponse.getStatus() == HttpStatus.OK.value()) {
            userService.view(userName);
        }
    }

过滤步骤:

完整的源码可以从这里获取。

上一篇下一篇

猜你喜欢

热点阅读