《Spring Security3》第三章第三部分翻译下(Remember me安全吗?)(转载)

Remember me 是否安全?

对我们精心保护的站点来说,为了用户体验而添加的任何与安全相关的功能,都有增加安全风险的潜在可能。按照其默认方式, Remember me 功能存在用户的 cookie 被拦截并被恶意用户重用的风险。下图展现了这种情况是如何发生的:


 

使用 SSL (第四章进行讨论)以及其他的网络安全技术能缓解这种类型攻击的风险,但是要注意的是还有其他技术如跨站脚本攻击( XSS )能够窃取或损害一个 remembered user session 。为了照顾用户的易用性,我们不会愿意让用户的财产信息或个人信息因为 remembered session 的不合理使用而遭到篡改或窃取。

 

【尽管我们不会涉及恶意用户行为的细节,但是当你实现安全系统时,了解恶意用户所使用的攻击技术是很重要的。 XSS 是其中的一种技术,当然还有其他的很多种。强烈建议你了解 OWASP Top Ten http://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project )作为一个入门列表并参考一本 web 应用安全参考书,里面介绍了各种的技术使用。】

 

平衡用户易用性和应用安全性的一种通用方法是识别出站点中与个人或敏感信息相关的功能点。确保这些功能点在进行授权校验时不仅要判断用户的角色,还要保证用户进行了完整的用户名和密码认证。这可以通过使用 SpEL 表达式语言的 fullyAuthenticated 伪属性来实现,关于授权规则的 SpEL 表达式语言我们在第二章中已经有所介绍。

Remember me 认证与完整认证的在认证规则上的区别

我们将在随后的第五章:精确的访问控制 中介绍高级的认证技术,但是,了解能够辨别认证 session 是否为 remembered 并以此建立访问规则也是很重要的。

 

我们可以设想一个使用 remembered session 登录的用户要查看和修改他的“ wish list ”。这与其他的客户在线站点很类似,并不会出现与用户信息或财务信息相关的风险(要注意的是每个站点各不相同,不能盲目的将这些规则应用与你的站点)。相反的,我们将会重点保护用户的账号以及订单功能。我们要确保即使是 remembered 的用户,如果试图访问账号信息或定购产品,都需要对他们进行认证。以下为我们如何设置授权规则:

 

 <intercept-url pattern="/login.do" access="permitAll"/>

Xml代码   收藏代码
  1. < intercept-url   pattern = "/account/*.do"   
  2.    access = "hasRole('ROLE_USER') and fullyAuthenticated" />   
  3. < intercept-url   pattern = "/*"   access = "hasRole('ROLE_USER')" />   

 已经存在的登录页和 ROLE_USER 设置没有变化。但我们添加了一条规则,要求用户具有 GrantedAuthority ROLE_USER 角色同时还要求用户被完全的认证,即这个认证的 session 确实是通过提供用户名、密码或等同的凭证来进行认证的。注意这里的 SpEL 逻辑操作语法——在 SpEL 中,使用 and or 以及 not 作为逻辑操作符。这是 SpEL 的设计者充分考虑的结果,因为 && 操作符在 XML 中很难被使用。

 

如果你在应用中尝试运行,如果以 remember me 功能登录并试图访问“ My Account ”链接,你将会得到一个 403 访问拒绝的提示,这说明这个地址已经被适当地保护了。出现错误界面是因为我们应用的配置还是使用默认的 AccessDeniedHandler ,这个类负责捕获和响应 AccessDeniedException 的信息。我们将会在第六章学习 AccessDeniedException 怎样被处理时,自定义这个行为。

 

【不使用表达式来实现完全认证的检查。如果你的应用不使用 SpEL 表达式来进行访问控制声明,你可以通过使用 IS_AUTHENTICATED_FULLY 访问规则来检查用户是不是进行了完整的认证(如: access=" IS_AUTHENTICATED_FULLY" )。但要注意的是,这种标准的角色设置声明并没有 SpEL 那样强的表现力,所以如果要处理复杂的 boolean 表达式的时候,可能会比较困难。】

 

错误处理尚没有添加,但是你可以看到通过这种方式将 remember me 的易用性与更高层次的安全性结合了起来,用户访问敏感的信息时就会被要求提供完整的凭证信息。

构建一个关联 IP Remember me Service

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

 

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

 

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

扩展 TokenBasedRememberMeServices

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

 

Java代码   收藏代码
  1. public   class  IPTokenBasedRememberMeServices  extends   
  2.     TokenBasedRememberMeServices {  

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

 

Java代码   收藏代码
  1. private   static   final  ThreadLocal<HttpServletRequest> requestHolder =   
  2. new  ThreadLocal<HttpServletRequest>();  
  3. public  HttpServletRequest getContext() {  
  4.     return  requestHolder.get();  
  5. }  
  6. public   void  setContext(HttpServletRequest context) {  
  7.     requestHolder.set(context);  
  8. }  
 

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

Java代码   收藏代码
  1. protected  String getUserIPAddress(HttpServletRequest request) {  
  2.   return  request.getRemoteAddr();  
  3. }  

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

Java代码   收藏代码
  1. @Override   
  2. public   void  onLoginSuccess(HttpServletRequest request,  
  3.     HttpServletResponse response,  
  4.     Authentication successfulAuthentication) {  
  5.   try   
  6.   {  
  7.     setContext(request);  
  8.     super .onLoginSuccess(request, response, successfulAuthentication  
  9.   }  
  10.   finally   
  11.   {    setContext(null );  
  12.   }  
  13. }  

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

Java代码   收藏代码
  1. @Override   
  2. protected  String makeTokenSignature( long  tokenExpiryTime,   
  3.     String username, String password) {  
  4.     return  DigestUtils.md5DigestAsHex((username +  ":"  +   
  5. tokenExpiryTime + ":"  + password +  ":"  + getKey() +  ":"  + getUserIPAdd  
  6. ress(getContext())).getBytes());   
  7. }  

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

Java代码   收藏代码
  1. @Override   
  2. protected   void  setCookie(String[] tokens,  int  maxAge,  
  3.   HttpServletRequest request, HttpServletResponse response) {  
  4.   // append the IP adddress to the cookie   
  5.   String[] tokensWithIPAddress =   
  6.       Arrays.copyOf(tokens, tokens.length+1 );  
  7.   tokensWithIPAddress[tokensWithIPAddress.length-1 ] =   
  8.       getUserIPAddress(request);  
  9.   super .setCookie(tokensWithIPAddress, maxAge,   
  10.       request, response);  
  11. }  

 这就得到了生成新 cookie 所有需要的信息。

 

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

 

 

Java代码   收藏代码
  1. @Override   
  2. protected  UserDetails processAutoLoginCookie(  
  3.   String[] cookieTokens,  
  4.   HttpServletRequest request, HttpServletResponse response)   
  5. {  
  6.   try   
  7.   {  
  8.     setContext(request);  
  9.   // take off the last token   
  10.     String ipAddressToken = cookieTokens[cookieTokens.length-1 ];  
  11.     if (!getUserIPAddress(request).equals(ipAddressToken))  
  12.     {  
  13.           throw   new  InvalidCookieException("Cookie IP Address did not   
  14. contain a matching IP (contained '" + ipAddressToken + "' )");  
  15.     }  
  16.         
  17.     return   super .processAutoLoginCookie(Arrays.copyOf(cookieTokens,   
  18. cookieTokens.length-1 ), request, response);  
  19.   }  
  20.   finally   
  21.   {  
  22.     setContext(null );  
  23.   }  
  24. }  

 我们的自定义的 RememberMeServices 编码已经完成了。现在我们要进行一些微小的配置。这个类的完整源代码(包括附加的注释)都在本章的源码中能够找到。

 

配置自定义的 RememberMeServices

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

 

 

Xml代码   收藏代码
  1. < bean   class = "com.packtpub.springsecurity.security.IPTokenBasedRememberMeServices"   id = "ipTokenBasedRememberMeServicesBean" >   
  2.   < property   name = "key" > < value > jbcpPetStore </ value > </ property >   
  3.   < property   name = "userDetailsService"   ref = "userService" />   
  4. </ bean >   

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

 

Xml代码   收藏代码
  1. < remember-me   key = "jbcpPetStore"    
  2.    services-ref = "ipTokenBasedRememberMeServicesBean" />   

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

 

Xml代码   收藏代码
  1. < user-service    id = "userService" >   

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

 

因为 remember me cookie Base64 编码的,我们能够使用一个 Base64 解码的工具得到 cookie 的值以证实我们的新增功能是否生效。如果我们这样做的话,我们能够看到一个名为 SPRING_SECURITY_REMEMBER_ME_COOKIE cookie 的内容大致如下所示:

guest:1251695034322:776f8ad44034f77d13218a5c431b7b34:127.0.0.1

 

正如我们所料,你能够看到 IP 地址确实存在于 cookie 的结尾处。在 IP 地址之前,你还能够分别看到用户名、时间戳以及 MD5 的哈希值。

 

【调试 remember me cookie 。在尝试调试 remember me 功能时,会有两个难点。第一个就是得到 cookie 的值本身! Spring Security 并没有提供记录我们设置的 cookie 值的日志级别。我们推荐使用基于浏览器的工具如 Mozilla Firefox 下的 Chris Pederick's Web Developer 插件( http://chrispederick.com/work/web-developer/ )。基于浏览器的开发工具一般允许查看(甚至编辑) cookie 的值。第二个困难(相对来说较小)就是解码 cookie 的值。你能使用在线或离线的 Base64 解码工具来对 cookie 的值进行解码(需要记住的是添加一个等号符( = )结尾,以使其成为一个合法的 Base64 编码值)。】

 

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

 

 

自定义 Remember me 的签名

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

 

 

Xml代码   收藏代码
  1. < bean   class ="com.packtpub.springsecurity.web.custom.  
  2. IPTokenBasedRememberMeServices" id = "ipTokenBasedRememberMeServicesBean" >   
  3. < property   name = "key" > < value > jbcpPetStore </ value > </ property >   
  4.   < property   name = "userDetailsService"   ref = "userService" />   
  5.   < property   name = "parameter"   value = "_remember_me" />   
  6.   < property   name = "cookieName"   value = "REMEMBER_ME" />   
  7. </ bean >   
 

 

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

(如果想更好的理解本章节内容,建议阅读一下 Spring Security 的源码——译者注)

猜你喜欢

转载自kongcodecenter.iteye.com/blog/1320015
今日推荐