用Spring Security实现HTTP Digest认证和同帐号多次登录限制

Spring Security的前身是Acegi,实际上要实现HTTP Digest认证可以不必采用Spring Security,而是直接使用Tomcat 容器提供的HTTP Digest认证功能即可,只是考虑到系统的扩展性和更多的可控性,最终决定采用Spring Security,在Javascript XMLHttpRequest与Spring Security配合实现具有鉴权认证功能的系统一帖中讲述了怎样使用HTTP Basic认证方式,本贴是上帖的续文,主要考虑到开发的系统是一个在线商业运营系统,足够的安全性是必须的。

目标系统是一个采用Javascript XMLHttpRequest对象的HTTP/XML通信系统,通信消息内容几乎全部是XML,所以采用Form based(基于表单提交)的认证并不方便,也不够安全。

在HTTP Digest方式下,服务器要保存session信息,而在HTTP Basic方式下,每次发起通信都可以携带用户帐号和口令信息,所以,在服务器上不必保存session状态,可以做一个测试,用上一帖的配置,客户端和服务器进行一次通信后,服务器虽然给客户端发下了一个JSESSIONID类的cookie,删掉后没有任何影响,本人没有深入研究服务器上哪个过程执行了cookie的下发。

由于HTTP Digest方式需要服务器维护session状态,所以需要考虑更多一些问题。而为了实现同帐号多次登录限制,需要使用ConcurrentSessionFilter,更加使session状态问题复杂化了。

在调试过程中一直在跟下面的异常作斗争:

[ERROR] 18:03:39 [metacamp] - Servlet.service() for servlet servlet名称 threw exception
java.lang.IllegalArgumentException: SessionIdentifierAware did not return a Session ID
(org.springframework.security.ui.WebAuthenticationDetails@3bcc: RemoteIpAddress: 127.0.0.1; SessionId: null)

起初,该异常发生在系统运行之初,第一次通信时,根据一些资料和实验,发现ConcurrentSessionFilter放在所有过滤器的前面,它内部需要使用SessionID作为关键字访问和修改状态信息,例如,查看和记录某个用户登录过几次。第一次通信当然是找不到这个信息的,很多网友也曾经跟这个异常战斗过,以前主要是为了解决ConcurrentSessionFilter(控制并发)和Remember me的冲突问题,也有人建议在发起安全保护的通信前先发一个普通的通信,使session能够预先建立好。

实际上,我遇到这个异常是对Spring Security使用不当,应该在web.xml中增加一个listener:

<listener>
<listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
这个bean能够发现当前没有可用的session并将该事件通知给能够创建session的其它bean,这样通信之初出现异常的问题解决了


Tomcat 容器的session超时时间缺省是30分钟,30分钟过后,再进行通信,上述异常又出现了,经过测试,发现HttpSessionContextIntegrationFilter.forceEagerSessionCreation=true应该设置,否则缺省是false,Spring 论坛上有对这个属性的解释,根据解释,如果session已经存在了,即使这个属性设置成true也不会重复创建(其英文有点难懂,是否理解有误?)。至此,系统已经安预期运行了。

以上总结主要来自于实践,未对Spring Security作深入的分析,可能理解有误。

ConcurrentSessionFilter的判断有点生硬

使用一个帐号登录以后,推出系统,重新启动浏览器,再次登录,被禁止,估计原来的session没有超时,本帐号用一个新的session登录,不能成功。要是能够判断客户端IP地址是否变化就好了。还想了一个替代解决方案:使用rememberMe,如果能够在客户端留下一个保存在硬盘中的cookie,也许能够解决这个问题,但是没有试成功。

并发会话控制问题解决了

参见[1]