首页 > 代码库 > 跟我学习Spring Security--在线宠物商店开发(五)

跟我学习Spring Security--在线宠物商店开发(五)

    我们知道将信息存在cookie中会存在一定的被拦截并被重用使用的风险,如图所示:

    技术分享

有一种让remember me功能更安全的方式就是将用户的IP地址绑定到cookie的内容上。让我们通过一个例子来描述怎样构建RememberMeServices的实现类来完成这个功能。

基本的实现方式是扩展o.s.s.web.authentication.rememberme.TokenBasedRememberMeServices基类,以添加请求者的IP地址到cookie本身和其他的MD5哈希元素中。

 

扩展这个基类涉及到重写两个主要方法,并重写或实现几个小的帮助方法。还有一个要注意的是我们需要临时存储HttpServletRequest(将使用它来得到用户的IP地址)到一个ThreadLocal中,因为基类中的一些方法并没有将HttpServletRequest作为一个参数。


首先,我们要扩展TokenBasedRememberMeServices类并重写父类的特定行为。尽管父类是非常易于重写,但是我们不想去重复一些重要的处理流程,所以能使这个类非常简明却有点不好理解。在com.packtpub.springsecurity.security包下创建这个类

public class IPTokenBasedRememberMeServices extends  
    TokenBasedRememberMeServices {

还有一些简单的方法来设置和获取ThreadLocal HttpServletRequest: 

private static final ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<HttpServletRequest>();  
			public HttpServletRequest getContext() {  
			    return requestHolder.get();  
			}  
			public void setContext(HttpServletRequest context) {  
			    requestHolder.set(context);  
			}

我们还需要添加一个工具方法以从HttpServletRequest中获取IP地址:

protected String getUserIPAddress(HttpServletRequest request) {  
  return request.getRemoteAddr();  
}

 我们要重写的第一个有趣的方法是onLoginSuccess,它用来为remember me处理设置cookie的值。在这个方法中,我们需要设置ThreadLocal并在完成处理后将其清除。需要记住的是父类方法的处理流程——收集用户的所有认证请求信息并将其合成到cookie中。

@Override  
public void onLoginSuccess(HttpServletRequest request,  
    HttpServletResponse response,  
    Authentication successfulAuthentication) {  
  try  
  {  
    setContext(request);  
    super.onLoginSuccess(request, response, successfulAuthentication  
  }  
  finally  
  {    setContext(null);  
  }  
}

 父类的onLoginSuccess方法将会触发makeTokenSignature方法来创建认证凭证的MD5哈希值。我们将要重写此方法,以实现从request中获取IP地址并使用Spring框架的一个工具类编码要返回的cookie值。(这个方法在进行remember me校验时还会被调用到,以判断前台传递过来的cookie值与后台根据用户名、密码、IP地址等信息生成的MD5值是否一致。

 

@Override  
protected String makeTokenSignature(long tokenExpiryTime,   
    String username, String password) {  
    return DigestUtils.md5DigestAsHex((username + ":" +   
tokenExpiryTime + ":" + password + ":" + getKey() + ":" + getUserIPAdd  
ress(getContext())).getBytes());   
}

 与之类似的,我们还重写了setCookie方法以添加包含IP地址的附加编码信息:

@Override  
			protected void setCookie(String[] tokens, int maxAge,  
			  HttpServletRequest request, HttpServletResponse response) {  
			  // append the IP adddress to the cookie  
			  String[] tokensWithIPAddress =   
			      Arrays.copyOf(tokens, tokens.length+1);  
			  tokensWithIPAddress[tokensWithIPAddress.length-1] =   
			      getUserIPAddress(request);  
			  super.setCookie(tokensWithIPAddress, maxAge,   
			      request, response);  
			}

最后,我们要重写processAutoLoginCookie方法,它用来校验用户端提供的remember me cookie的内容。父类已经为我们解决了大部分有意思的工作,但是,为了避免调用父类冗长的代码,我们在调用它之前先进行了一次IP地址的校验

@Override  
			protected UserDetails processAutoLoginCookie(  
			  String[] cookieTokens,  
			  HttpServletRequest request, HttpServletResponse response)   
			{  
			  try  
			  {  
			    setContext(request);  
			  // take off the last token  
			    String ipAddressToken = cookieTokens[cookieTokens.length-1];  
			    if(!getUserIPAddress(request).equals(ipAddressToken))  
			    {  
			          throw new InvalidCookieException("Cookie IP Address did not contain a matching IP (contained ‘" + ipAddressToken + "‘)");  
			    }  
			        
			    return super.processAutoLoginCookie(Arrays.copyOf(cookieTokens,   
			cookieTokens.length-1), request, response);  
			  }  
			  finally  
			  {  
			    setContext(null);  
			  }  
			}

我们的自定义的RememberMeServices编码已经完成了。现在我们要进行一些微小的配置。


配置自定义的RememberMeServices实现需要两步来完成。第一步是修改dogstore-base.xml Spring配置文件,以添加我们刚刚完成类的Spring Bean声明:

<bean class="com.packtpub.springsecurity.security.IPTokenBasedRememberMeServices" id="ipTokenBasedRememberMeServicesBean">  
  <property name="key"><value>jbcpPetStore</value></property>  
  <property name="userDetailsService" ref="userService"/>  
</bean>

 第二个要进行的修改是Spring Security的XML配置文件。修改<remember-me>元素来引用自定义的Spring Bean,如下所示:

<remember-me key="jbcpPetStore"   
   services-ref="ipTokenBasedRememberMeServicesBean"/>

 最后为<user-service>声明添加一个id属性,如果它还没有添加的话:

<user-service  id="userService">

重启web应用,你将能看到新的IP过滤功能已经生效了。

如果用户是在一个共享的或负载均衡的网络设施下,如multi-WAN公司环境,基于IP的remember me tokens可能会出现问题。但是在大多数场景下,添加IP地址到remember me功能能够为用户提供功能更强、更好的安全层.

 

remember me form的checkbox名(_spring_security_remember_me)以及cookie的名(SPRING_SECURITY_REMEMBER_ME_COOKIE),是否能够修改?<remember-me>声明是不支持这种扩展性的,但是现在我们作为一个Spring Bean声明了自己的RememberMeServices实现,那我们能够定义更多的属性来改变checkbox和cookie的名字:

<bean class="com.packtpub.springsecurity.web.custom.  
IPTokenBasedRememberMeServices" id="ipTokenBasedRememberMeServicesBean">  
<property name="key"><value>jbcpPetStore</value></property>  
  <property name="userDetailsService" ref="userService"/>  
  <property name="parameter" value="http://www.mamicode.com/_remember_me"/>  
  <property name="cookieName" value="http://www.mamicode.com/REMEMBER_ME"/>  
</bean>

不要忘记的是,还需要修改login.jsp页面中的checkbox form域以与我们声明的parameter值相匹配。我们建议你进行一下实验以确保理解这些设置之间的关联。


本文出自 “进击的程序猿” 博客,请务必保留此出处http://zangyanan.blog.51cto.com/11610700/1876133

跟我学习Spring Security--在线宠物商店开发(五)