但是,在Servlet规范中,我们还可以。如果要在Spring MVC中使用Filter,应该怎么做?

    有的童鞋在上一节的Web应用中可能发现了,如果注册时输入中文会导致乱码,因为Servlet默认按非UTF-8编码读取参数。为了修复这一问题,我们可以简单地使用一个EncodingFilter,在全局范围类给HttpServletRequestHttpServletResponse强制设置为UTF-8编码。

    可以自己编写一个EncodingFilter,也可以直接使用Spring MVC自带的一个CharacterEncodingFilter。配置Filter时,只需在web.xml中声明即可:

    因为这种Filter和我们业务关系不大,注意到CharacterEncodingFilter其实和Spring的IoC容器没有任何关系,两者均互不知晓对方的存在,因此,配置这种Filter十分简单。

    我们再考虑这样一个问题:如果允许用户使用Basic模式进行用户验证,即在HTTP请求中添加头Authorization: Basic email:password,这个需求如何实现?

    编写一个AuthFilter是最简单的实现方式:

    1. @Component
    2. public class AuthFilter implements Filter {
    3. @Autowired
    4. UserService userService;
    5. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    6. throws IOException, ServletException {
    7. HttpServletRequest req = (HttpServletRequest) request;
    8. // 获取Authorization头:
    9. String authHeader = req.getHeader("Authorization");
    10. if (authHeader != null && authHeader.startsWith("Basic ")) {
    11. // 从Header中提取email和password:
    12. String email = prefixFrom(authHeader);
    13. String password = suffixFrom(authHeader);
    14. // 登录:
    15. User user = userService.signin(email, password);
    16. // 放入Session:
    17. req.getSession().setAttribute(UserController.KEY_USER, user);
    18. }
    19. // 继续处理请求:
    20. chain.doFilter(request, response);
    21. }

    现在问题来了:在Spring中创建的这个AuthFilter是一个普通Bean,Servlet容器并不知道,所以它不会起作用。

    所以,得通过一种方式,让Servlet容器实例化的Filter,间接引用Spring容器实例化的AuthFilter。Spring MVC提供了一个DelegatingFilterProxy,专门来干这个事情:

    1. <web-app>
    2. <filter>
    3. <filter-name>authFilter</filter-name>
    4. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    5. </filter>
    6. <filter-mapping>
    7. <filter-name>authFilter</filter-name>
    8. <url-pattern>/*</url-pattern>
    9. </filter-mapping>
    10. ...
    11. </web-app>

    我们来看实现原理:

    1. Servlet容器从web.xml中读取配置,实例化DelegatingFilterProxy,注意命名是authFilter
    2. Spring容器通过扫描@Component实例化AuthFilter

    DelegatingFilterProxy生效后,它会自动查找注册在ServletContext上的Spring容器,再试图从容器中查找名为authFilter的Bean,也就是我们用@Component声明的AuthFilter

    DelegatingFilterProxy将请求代理给AuthFilter,核心代码如下:

    这就是一个代理模式的简单应用。我们画个图表示它们之间的引用关系如下:

    1. ┌─────────────────────┐ ┌───────────┐
    2. DelegatingFilterProxy│─│─│─ ─>│AuthFilter
    3. └─────────────────────┘ └───────────┘
    4. ┌─────────────────────┐ ┌───────────┐
    5. DispatcherServlet │─ ─>│Controllers
    6. Servlet Container Spring Container

    如果在web.xml中配置的Filter名字和Spring容器的Bean的名字不一致,那么需要指定Bean的名字:

    1. <filter>
    2. <filter-name>basicAuthFilter</filter-name>
    3. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    4. <!-- 指定Bean的名字 -->
    5. <init-param>
    6. <param-name>targetBeanName</param-name>
    7. <param-value>authFilter</param-value>
    8. </init-param>
    9. </filter>

    实际应用时,尽量保持名字一致,以减少不必要的配置。

    对其进行Base64编码,最终构造出的Header如下:

    1. Authorization: Basic dG9tJTQwZXhhbXBsZS5jb206dG9tY2F0

    使用如下的curl命令并获得响应如下:

    1. $ curl -v -H 'Authorization: Basic dG9tJTQwZXhhbXBsZS5jb206dG9tY2F0' http://localhost:8080/profile
    2. > GET /profile HTTP/1.1
    3. > Host: localhost:8080
    4. > User-Agent: curl/7.64.1
    5. > Accept: */*
    6. > Authorization: Basic dG9tJTQwZXhhbXBsZS5jb206dG9tY2F0
    7. >
    8. < HTTP/1.1 200
    9. < Set-Cookie: JSESSIONID=CE0F4BFC394816F717443397D4FEABBE; Path=/; HttpOnly
    10. < Content-Type: text/html;charset=UTF-8
    11. < Content-Language: en-CN
    12. < Transfer-Encoding: chunked
    13. < Date: Wed, 29 Apr 2020 00:15:50 GMT
    14. <
    15. ...HTML输出...

    上述响应说明AuthFilter已生效。

    注意:Basic认证模式并不安全,本节只用来作为使用Filter的示例。

    从下载练习:使用DelegatingFilterProxy实现AuthFilter (推荐使用快速下载)

    当一个Filter作为Spring容器管理的Bean存在时,可以通过间接地引用它并使其生效。

    集成Filter - 图1