第六章 高级配置和扩展
到目前为止,我们已经介绍了大多数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过滤器的功能以添加我们尚未描述到的功能——动手是学习的最好方法,而将练习改造成现实世界中的例子是将抽象概念变得具体的很好方式。