阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

集成Filter

38次阅读
没有评论

共计 4199 个字符,预计需要花费 11 分钟才能阅读完成。

在 Spring MVC 中,DispatcherServlet只需要固定配置到 web.xml 中,剩下的工作主要是专注于编写 Controller。

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

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

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

<web-app>
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

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

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

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

@Component
public class AuthFilter implements Filter {@Autowired
    UserService userService;

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) request;
        // 获取 Authorization 头:
        String authHeader = req.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Basic")) {// 从 Header 中提取 email 和 password:
            String email = prefixFrom(authHeader);
            String password = suffixFrom(authHeader);
            // 登录:
            User user = userService.signin(email, password);
            // 放入 Session:
            req.getSession().setAttribute(UserController.KEY_USER, user);
        }
        // 继续处理请求:
        chain.doFilter(request, response);
    }
}

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

如果我们直接在 web.xml 中声明这个 AuthFilter,注意到AuthFilter 的实例将由 Servlet 容器而不是 Spring 容器初始化,因此,@Autowire根本不生效,用于登录的 UserService 成员变量永远是null

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

<web-app>
    <filter>
        <filter-name>authFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>authFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

我们来看实现原理:

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

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

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

public class DelegatingFilterProxy implements Filter {private Filter delegate;
    public void doFilter(...) throws ... {if (delegate == null) {delegate = findBeanFromSpringContainer();
        }
        delegate.doFilter(req, resp, chain);
    }
}

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

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
  ┌─────────────────────┐        ┌───────────┐   │
│ │DelegatingFilterProxy│─│─│─ ─▶│AuthFilter │
  └─────────────────────┘        └───────────┘   │
│ ┌─────────────────────┐ │ │    ┌───────────┐
  │  DispatcherServlet  │─ ─ ─ ─▶│Controllers│   │
│ └─────────────────────┘ │ │    └───────────┘
                                                 │
│    Servlet Container    │ │  Spring Container
 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─   ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

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

<filter>
    <filter-name>basicAuthFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <!-- 指定 Bean 的名字 -->
    <init-param>
        <param-name>targetBeanName</param-name>
        <param-value>authFilter</param-value>
    </init-param>
</filter>

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

要使用 Basic 模式的用户认证,我们可以使用 curl 命令测试。例如,用户登录名是 [email protected],口令是tomcat,那么先构造一个使用 URL 编码的 用户名: 口令 的字符串:

tom%40example.com:tomcat

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

Authorization: Basic dG9tJTQwZXhhbXBsZS5jb206dG9tY2F0

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

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

上述响应说明 AuthFilter 已生效。

注意

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

练习

使用 DelegatingFilterProxy 实现 AuthFilter。

下载练习

小结

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

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计4199字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中