实施Remember-Me认证
原文:
http://tech.it168.com/j/2007-11-05/200711051740968_2.shtml
一、Remember-Me认证功能介绍
很多网站的用户登录页面都提供了一个类似于“两周内不用再登录”、“记住我的帐号”等功能,其原理是在用户登录成功后使用客户端浏览器的Cookie记录用户登录信息,当下次再访问相同站点时,直接从Cookie中取得用户登录信息并进行自动登录。这即是经典的Remember-Me的功能,该功能在一定程度上降低了用户频繁登录的麻烦。根据系统安全性需求的不同,Remember-Me可能在Cookie中保存用户名/密码或仅保存用户名,前者可以完成自动登录,而后者只是让用户避免输入用户名。
如果在Cookie中记录用户名/密码,虽然可以避免每次访问网站都进行登录的麻烦,但这把双刃剑的反面是黑客可以在一定条件下获取这个免检的通行证。为了在给用户带来便利的同时尽力降低潜在的风险,Cookie保存用户名/密码的方式变得非常关键,以下几点是必须考虑的问题:
1) Cookie是易受攻击的,多用户共享浏览器和跨站点脚本攻击都可能使Cookie失窃;
2)将用户名/密码保存在Cookie中,意味着用户可以在不显式进行登录的情况下,获取正常登录的一切权限;
3)一切可以从Cookie中反推出密码明文的存储方式都是不可接受的;
4)必须将客户端IP信息绑定在Cookie中,这样即使Cookie失窃,也不可能在其它机器使用。
如果说HttpSessionContextIntegrationFilter通过HttpSession使Authentication获得跨请求共享的能力,那么Remember-Me则通过Cookie使Authentication获得跨多个Session的能力。Remember-Me功能可以视为一套解决方案,以下是Remember-Me中最关键的三个问题:
1) 在用户登录时,获取用户名/密码的信息,并将其以一定方式保存到Cookie中;
2) 在Cookie有效时间内,当用户访问站点安全页面时,自动进行登录;
3) 必须提供一个功能,让用户可以手工清除Remember-Me Cookie。
org.acegisecurity.ui.rememberme.RememberMeServices是Remember-Me方案中最关键的一个接口,它定义了以下几个方法:
l void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication):登录成功后调用该方法,将用户名/密码保存到Cookie中;
l void loginFail(HttpServletRequest request, HttpServletResponse response):登录失败后调用该方法;
l Authentication autoLogin(HttpServletRequest request, HttpServletResponse response):从Cookie中自动获取用户名/密码进行自动登录。
loginSuccess()和loginFail()方法的调用已经编制到Acegi的AbstractProcessingFilter抽象过滤器中,这意味着任何注入了RememberMeServices实例的过滤器都会以适合的方式调用这两个方法。而autoLogin()方法则通过RememberMeProcessingFilter进行调用,当RememberMeProcessingFilter发现SecurityContextHolder中不存在有效的Authentication时,autoLogin()方法就会被执行。
Acegi为RememberMeServices接口提供了两个实现类,它们分别是:
l NullRememberMeServices:类似于适配器的实现类,它不做任何有意义的事情,这是AbstractProcessingFilter中默认的实现类;
l TokenBasedRememberMeServices:基于凭证(一般指用户名/密码)的Remember-Me实现类,它真实地实现了接口中的方法。
二、Remember-Me认证的代码实现
在登录时将用户名/密码记录到Cookie中
我们第一个要做的工作是通过调整AuthenticationProcessingFilter的配置,在处理用户登录页面提交的用户认证信息时,将用户名/密码通过Response记录到客户端的Cookie中:
代码清单 12 applicationContext-acegi-security.xml
记录Remember-Me的Cookie信息
… <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"> … ①注入一个RememberMeServices <property name="rememberMeServices" ref="rememberMeServices"/> </bean> <bean id="rememberMeServices" ②RememberMeServices配置 class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices"> <property name="tokenValiditySeconds" value="432000"/> ②-1Cookie有效时间,单位为秒 <property name="key" value="baobaotao"/> ②-2 Cookie中的键值 </bean> …
三、Remember-Me认证的代码分析
(1)
通过以上的配置,我们在处理用户登录的同时将用户名/密码的信息记录到Cookie中。authenticationProcessingFilter在完成用户身份认证后,如果认证成功,调用rememberMeServices的loginSuccess()方法,该方法将用户名/密码按以下方式进行编码,编码后再写到客户端的Cookie中:
base64(username + ":" + expirationTime + ":" + md5Hex(username + ":" + expirationTime + ":"+password + ":" + key))base64()表示进行BASE64编码操作,而md5Hex()表示进行MD5摘要并将结果值以HEX(十六进制)进行编码。注意计算式中粗体所示key操作项,Acegi通过key防止整个加密串被恶意篡改。因为key是在服务端中指定的值,黑客无法进行猜测,在服务端通过如②-2所示的key属性指定该值。
(2)
Cookie的有效时间通过tokenValiditySeconds指定,默认为两个星期。②-1处我们显式指定为5天(对应的秒数)。
(3)
我们知道authenticationProcessingFilter处理对应/j_acegi_security_check的请求,我们应该让用户决定是否使用Remember-Me的功能,这可以通过一个名为“_acegi_security_remember_me”的HTTP参数来决定,当登录请求表单包含该参数时(勾选上对应的复选框),服务端认为需要启用Remember-Me功能,否则不启用Remember-Me功能。所以,我们必须相应地调整登录页面表单。
四、index.jsp:添加是否使用Remember-Me功能的控制参数
<%@ page contentType="text/html;charset=UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> … <form name="form1" method="post" action="<c:url value="/j_acegi_security_check"/>"> 用户名:<input type="text" name="j_username"/><br/> 密 码:<input type="password" name="j_password"/><br/> ①用户可以通过勾选或取消该复选框决定是否启用Remember-Me功能 <input type="checkbox" name="_acegi_security_remember_me">5天内不用再登录 <input type="submit" value="登录"/> </form> …