第六章 高级配置和扩展
到目前为止,我们已经介绍了大多数 Spring Security 组件的理论以及架构和使用。我们的 JBCP Pets 商业站点也在逐渐变成一个安全的 web 应用,我们将会深入讲解一些更有难度的挑战。
在本章的课程中,我们将会:
l 实现我们自己的安全过滤器,解决一个很有趣的问题,即对特定的用户角色用 IP 过滤的方式增强站点的安全;
l 构建自定义的 AuthenticationProvider 及需要的支持类;
l 理解和实现反黑客的措施名为 session 固化防护( session fixation protection )以及 session 的并发控制;
l 使用包括 session 并发控制等功能构建简单的用户 session 报告增强;
l 配置以及自定义访问拒绝后的行为和异常处理;
l 构建基于 Spring bean 的 Spring Security 配置,放弃使用便利的安全命名空间 <http> 配置风格,从头开始直接织入和实例化完整的 Spring Security 大量类;
l 了解如何通过基于 Spring bean 的方式配置 session 的处理和创建;
l 对比 <http> 配置风格相对于基于 Spring bean 配置风格的优劣;
l 学习 AuthenticationEvent 的架构以及事件处理和自定义;
l 构建一个自定义的 SpEL 表达式投票器,新建一个 SpEL 方法并在 <intercept-url> 表达式中使用。
实现一个自定义的安全过滤器
对于安全应用来说,一个很常见的定制化场景就是使用自定义的 servlet 过滤器,它能够用来增加应用特定的安全层,通过提供更完整的信息增强用户体验,并移除潜在的恶意行为。
在 servlet 过滤器级别实现 IP 过滤
一个能够使得 JBCP Pets 安全审计人员很高兴的功能增强就是围绕管理员账号的使用进行更强限制,或者(更好的是)针对所有用户对站点的管理操作。
我们通过一个过滤器来解决这个问题,保证所有具有 ROLE_ADMIN 角色的用户只能在一系列特定的 IP 地址上访问系统。我们将会在此做简单的地址匹配,但是你可以很容易地扩展这个例子,来使用 IP 掩码、从数据库中读取 IP 地址等。
细致的用户可能会意识到会有其它的方法来实现这个功能,包括更复杂的 <intercept-url> 访问声明;但是,为了阐述的方便,这是一个很简单直接的例子。记住在现实世界中,诸如网络地址转换( Network Address Translation , NAT )以及动态 IP 地址会使得基于 IP 的规则在公网或无管理的网络中很脆弱。
书写我们自己的 servlet 过滤器
我们的过滤器将会设置成目标角色以及特定的 IP 地址才能允许访问。我们将这个类命名为 com.packtpub.springsecurity.security.IPRoleAuthenticationFilter ,并定义如下。这个代码有一些复杂,所以省略掉了一下不重要的代码列表。请查看本章的源代码以了解此类:
- package com.packtpub.springsecurity.security;
- // imports omitted
- public class IPRoleAuthenticationFilter extends OncePerRequestFilter
- {}
可以看到,我们的过滤器继承自 Spring web 库中的 o.s.web.filter.OncePerRequestFilter 基类。但这并不是必须的,这对于具有复杂配置且至执行一次的过滤器来说很便利,所以我们建议使用。
- private String targetRole;
- private List<String> allowedIPAddresses;
我们的过滤器具有两个属性——目标角色(如 ROLE_ADMIN ),以及一个允许的 IP 地址列表。这将会通过标准的 Spring bean 定义进行配置,我们将会稍后见到。最后,我们到达这个 bean 的核心,也就是 doFilterInternal 方法。
- @Override
- public void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
- // before we allow the request to proceed, we'll first get the user's role
- // and see if it's an administrator
- final Authentication authentication = SecurityContextHolder.
- getContext().getAuthentication();
- if (authentication != null && targetRole != null ) {
- boolean shouldCheck = false ;
- // look if the user is the target role
- for (GrantedAuthority authority : authentication.getAuthorities()) {
- if (authority.getAuthority().equals(targetRole)) {
- shouldCheck = true ;
- break ;
- }
- }
- // if we should check IP, then check
- if (shouldCheck && allowedIPAddresses.size() > 0 ) {
- boolean shouldAllow = false ;
- for (String ipAddress : allowedIPAddresses) {
- if (req.getRemoteAddr().equals(ipAddress)) {
- shouldAllow = true ;
- break ;
- }
- }
- if (!shouldAllow) {
- // fail the request
- throw new AccessDeniedException(“Access has been
- denied for your IP address: “+req.getRemoteAddr());
- }
- }
- } else {
- logger.warn(“The IPRoleAuthenticationFilter should be placed
- after the user has been authenticated in the filter chain.”);
- }
- chain.doFilter(req, res);
- }
- // accessors (getters and setters) omitted
- }
你可以看到,代码很简单明了,使用 SecurityContext 来获得 Authentication 关于当前请求的信息,就像我们在前面的章节练习中所作的那样。你可能会意识到没有很多特定与 Spring Security 的东西,除了获取用户角色( GrantedAuthority )的方法以及使用了 AccessDeniedException 来标示访问被拒绝。
让我们看一下如何作为一个 Spring bean 配置自定义的过滤器。
配置 IP servlet 过滤器
配置这个过滤器作为一个简单的 Spring bean 。我们可以在 dogstore-base.xml 文件中配置它。
- < bean id = "ipFilter" class = "com.packtpub.springsecurity .security.IPRoleAuthenticationFilter" >
- < property name = "targetRole" value = "ROLE_ADMIN" />
- < property name = "allowedIPAddresses" >
- < list >
- < value > 1.2.3.4 </ value >
- </ list >
- </ property >
- </ bean >
使用标准的 Spring bean 配置语法,我们能够提供一系列的 IP 地址值。在本例中, 1.2.3.4 显然不是合法的 IP 地址(如果本地部署的话, 127.0.0.1 会是不错的一个 IP 地址配置),但是它为我们提供了便利的方式来测试这个过滤器是否生效。
最后,我们要将这个过滤器添加到 Spring Security 的过滤器链中。
将 IP servlet 过滤器添加到 Spring Security 过滤器链中
要添加到 Spring Security 过滤器链中的 Servlet 过滤器要通过相对于已经存在于过滤器链中其它过滤器来确定位置。自定义的过滤器要在 <http> 元素中配置,通过一件简单 bena 引用和位置标示, IP servlet 过滤器的配置如下:
- < http >
- < custom-filter ref = "ipFilter" before = "FILTER_SECURITY_INTERCEPTOR" />
- </ http >
需要记住的是我们的过滤器依赖于 SecurityContext 和 Authentication 对象,来进行辨别用户的 GrantedAuthority 。所以,我们要将这个过滤器的位置放在 FilterSecurityInterceptor 之前,它(你可能会回忆起第二章: Spring Security 起步 )负责确定用户是否有正确的权限访问系统。在过滤器的这个点上,用户的标示信息已经知道了,所以这是一个合适的位置以插入我们的过滤器。
现在你可以重启应用,因为这个新的 IP 过滤器,作为 admin 用户登录将会被限制。你可以对其进行随意的修改,以完全理解各部分是如何协同的。
【扩展 IP 过滤器。对于更复杂的需求,可以扩展这个过滤器以允许对角色和 IP 地址(子网匹配, IP 段等)更复杂的匹配。但是,在 java 中并没有广泛使用的类库来进行这种类型的计算——考虑到安全环境中普遍存在的 IP 过滤,这一点颇令人吃惊。】
尝试增强 IP 过滤器的功能以添加我们尚未描述到的功能——动手是学习的最好方法,而将练习改造成现实世界中的例子是将抽象概念变得具体的很好方式。