成功登录系统的后置处理
一般的业务系统在用户登录成功后,需要在数据库中记录一条相应的用户登录日志。我们可以通过Acegi提供的事件机制来完成这一功能。当用户身份认证成功后,Acegi会产生一个AuthenticationSuccessEvent事件,该事件是org.springframework.context.ApplicationEvent的子类,所以AuthenticationSuccessEvent是一个Spring容器事件。我们可以定义一个监听器响应该事件以记录用户登录日志,请看LoginSuccessListener的代码:
代码清单 9 LoginSuccessListener
<!---->package com.baobaotao.service;
import org.acegisecurity.Authentication;
import org.acegisecurity.event.authentication.AuthenticationSuccessEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
public class LoginSuccessListener implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof AuthenticationSuccessEvent) {①判断是否是认证成功的事件
AuthenticationSuccessEvent authEvent = (AuthenticationSuccessEvent)event;
Authentication auth = authEvent.getAuthentication();
String userName = auth.getName();
②这里,我们仅通过一条输出信息的语句模拟记录登录日志的操作
System.out.println("模拟记录用户["+userName+"]成功登录日志...");
}
}
}
通过扩展Spring的ApplicationListener接口定义一个监听Acegi AuthenticationSuccessEvent事务的监听器。在实际系统中,你可以使用DAO替换②处的逻辑,执行记录用户登录日志的操作。在编写好LoginSuccessListener后,剩下的工作就是在Spring容器中声明这个监听器,仅需要一行简单的配置就可以了:
<!----><bean class="com.baobaotao.service.LoginSuccessListener"/>
这样,当用户登录并通过了Acegi的用户身份认证后, LoginSuccessListener监听器的onApplicationEvent()方法将接收到AuthenticationSuccessEvent事件。
在多个请求之间共享SecurityContext
我们知道SecurityContext保持着Acegi框架重要的Authentication对象,而SecurityContext保存在SecurityContextHolder中。然而SecurityContextHolder只为SecurityContext对象提供请求线程范围内的生命周期。也就是说,当一个登录认证请求结束后,用户相关的SecurityContext对象将从SecurityContextHolder中清除了。当用户发起下一个请求时,又必须重新进行身价认证,如何使SecurityContext在Session级别中进行共享呢?
Acegi通过HttpSessionContextIntegrationFilter解决这个问题,当一个请求到达时,它尝试从Session中获取用户关联的SecurityContext并将其放入到SecurityContextHolder中,当请求结束时,HttpSessionContextIntegrationFilter又将SecurityContext转存到HttpSession中。这样,Acegi就通过HttpSessionContextIntegrationFilter将SecurityContext对象在请求级的SecurityContextHolder和Session级的HttpSession中摆渡,从而保证SecurityContext可以在多个请求之间共享。
在过滤器链中,HttpSessionContextIntegrationFilter必须位于认证处理过滤器之前,这样认证处理过滤器当发现SecurityContextHolder中已经拥有和用户关联的经过认证的Authentication时,就可以短路掉用户身份认证的步骤:
代码清单 10 applicationContext-acegi-plugin.xml
通过HttpSession转存请求之间的SecurityContext
<!----><bean id="filterChainProxy"
class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
…
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter ①
</value>
</property>
</bean>
<bean id="httpSessionContextIntegrationFilter" ②通过HttpSession转存SecurityContext的过滤器
class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
…
在①处,我们将httpSessionContextIntegrationFilter放置在authenticationProcessingFilter之前。如果用户还未通过身份认证,httpSessionContextIntegrationFilter在HttpSession中找不到对应的SecurityContext,这时authenticationProcessingFilter将启用正常的认证流程。反之,如果已经通过了身份认证,SecurityContext将直接从HttpSession中获取。
退出系统的后置处理
SecurityContext保存在HttpSession中,当用户退出系统时必须清除之,否则SecurityContext将一直保存在HttpSession中,需要等到Session过期后才会被清除,这将造成额外的内存消耗。从另外一个方面说,在退出系统时常常需要执行一些相关的操作,如记录用户退出系统的日志、将登录信息保存到Cookie中等。
Acegi为完成以上一系列由退出系统引发的操作,专门提供了一个退出过滤器:org.acegisecurity.ui.logout.LogoutFilter,它允许我们通过配置完成相关的操作:
代码清单 11 applicationContext-acegi-plugin.xml
退出系统后置处理配置
<!----><bean id="filterChainProxy"
class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
…
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,logoutFilter ①
</value>
</property>
</bean>
<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">②退出系统过滤器
<constructor-arg> ②-1退出系统前需要执行的操作
<list>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler" />
</list>
</constructor-arg>
<constructor-arg value="/index.jsp" />②-2退出系统后转向的URL
②-3用于响应退出系统请求的URL
<property name="filterProcessesUrl" value="/j_acegi_logout"/>
</bean>
…
在①处,我们在过滤器链中添加一个logoutFilter,它负责处理用户退出系统的操作。退出系统过滤器需要以下三方面的信息:
1) 哪一个URL是退出系统的HTTP请求,这通过filterProcessesUrl属性指定,②-3所示。LogoutFilter默认的退出系统URL即是“/j_acegi_logout”,这里显式进行配置是为了说明可以根据需要进行调整;
2) 退出系统时需要执行哪些处理器,通过LogoutFilter的构造函数指定,如②-1所示。处理器必须实现org.acegisecurity.ui.logout.LogoutHandler接口,Acegi为该接口提供了两个实现类,分别是SecurityContextLogoutHandler和 TokenBasedRememberMeServices,前者将SecurityContext从HttpSession中删除,而后者将Anthentication中的用户名/密码保存到客户端的Cookie中,以便下次用户访问系统时直接通过Cookie中的用户名/密码进行自动登录。我们将在下一节学习到TokenBasedRememberMeServices的知识。
3) 退出系统后转向哪个URL,通过构造函数参数指定,如②-2所示。
配置好退出系统过滤器后,在需要在系统页面中提供一个退出系统的操作链接:
<A href="<c:url value="/j_acegi_logout"/>">退出系统</A>
注意粗体所示代码代表退出系统所对应的URL,它必须和LogoutFilter的filterProcessesUrl属性一致。这样,当用户点击页面中的“退出系统”链接后,LogoutFilter拦截这个URL请求,并调用SecurityContextLogoutHandler将SecurityContext从HttpSession中清除掉,最后转向/index.jsp页面。
实施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实现类,它真实地实现了接口中的方法。
在登录时将用户名/密码记录到Cookie中
我们第一个要做的工作是通过调整AuthenticationProcessingFilter的配置,在处理用户登录页面提交的用户认证信息时,将用户名/密码通过Response记录到客户端的Cookie中:
代码清单 12 applicationContext-acegi-plugin.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>
…
通过以上的配置,我们在处理用户登录的同时将用户名/密码的信息记录到Cookie中。authenticationProcessingFilter在完成用户身份认证后,如果认证成功,调用rememberMeServices的loginSuccess()方法,该方法将用户名/密码按以下方式进行编码,编码后再写到客户端的Cookie中:
<!---->base64(username + ":" + expirationTime + ":" + md5Hex(username + ":" + expirationTime + ":"+password + ":"
+ key))
base64()表示进行BASE64编码操作,而md5Hex()表示进行MD5摘要并将结果值以HEX(十六进制)进行编码。注意计算式中粗体所示key操作项,Acegi通过key防止整个加密串被恶意篡改。因为key是在服务端中指定的值,黑客无法进行猜测,在服务端通过如②-2所示的key属性指定该值。
Cookie的有效时间通过tokenValiditySeconds指定,默认为两个星期。②-1处我们显式指定为5天(对应的秒数)。
我们知道authenticationProcessingFilter处理对应/j_acegi_security_check的请求,我们应该让用户决定是否使用Remember-Me的功能,这可以通过一个名为“_acegi_security_remember_me”的HTTP参数来决定,当登录请求表单包含该参数时(勾选上对应的复选框),服务端认为需要启用Remember-Me功能,否则不启用Remember-Me功能。所以,我们必须相应地调整登录页面表单,如代码清单 13所示:
代码清单 13 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>
…
根据Remember-Me进行自动登录
如果用户在登录时选择了Remember-Me的功能(即勾选“5天内不用再登录”复选框),登录成功后用户名/密码的信息就保存在客户机的Cookie中。下次用户直接访问站点的安全页面时,必须有一个过滤器能够自动调用RememberMeServices#autoLogin()完成自动登录的操作,这便是通过RememberMeProcessingFilter过滤器来完成的:
代码清单 14 applicationContext-acegi-plugin.xml
<!---->Remember-Me自动登录
<bean id="filterChainProxy"
class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
…
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,
logoutFilter,rememberMeProcessingFilter ①处理自动登录的过滤器
</value>
</property>
</bean>
<bean id="rememberMeProcessingFilter" ②自动登录过滤器
class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
<property name="rememberMeServices" ref="rememberMeServices"/>②-1
<property name="authenticationManager" ref="authenticationManager"/>②-2
</bean>
<bean id="rememberMeServices" ③
class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<property name="tokenValiditySeconds" value="432000"/>
<property name="key" value="baobaotao"/>
<property name="userDetailsService" ref="userDetailsService" />③-1
</bean>
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
<bean class="org.acegisecurity.providers.rememberme. ④
RememberMeAuthenticationProvider">
<property name="key" value=" baobaotao"/>
</bean>
</list>
</property>
</bean>
首先,我们在过滤器链中添加一个rememberMeProcessingFilter,它负责对所有HTTP请求进行拦截,当发现SecurityContextHolder中没有包含有效的Authentication对象时,自动调用RememberMeServices#autoLogin()方法从Cookie中获取用户名/密码的编码串进行自动登录。所以rememberMeProcessingFilter首先要注入一个RememberMeServices Bean,如②-1所示。
在 代码清单 12中已经定义了一个被AuthenticationProcessingFilter 使用的RememberMeServices Bean。我们在前面说过, AuthenticationProcessingFilter在注入RememberMeServices Bean后,就会在适合的时候调用RememberMeServices#loginSuccess()方法将用户凭证保存到Cookie中。而RememberMeProcessingFilter则会调用RememberMeServices#autoLogin()方法对保存在Cookie中的用户凭证执行自动认证的操作。前者是将Authentication中的用户名/密码写入到Cookie中,而后者需要据此获得对应的UserDetails,进而再重现出Authentication对象。正如你所想到的一样,RememberMeServices需要通过一个UserDetailsService来完成这项工作,所以我们需要调整RememberMeServices的配置,如③-1所示。
rememberMeProcessingFilter通过rememberMeServices获取对应Cookie中用户的UserDetails后,就必须进行用户身份认证。这项工作依然委托给authenticationManager完成,所以在②-2中,我们给rememberMeProcessingFilter注入了authenticationManager Bean。
authenticationManager如何对基于Cookie的用户凭证进行认证呢?显然,不能采用原来的daoAuthenticationProvider所用的方法,因为Cookie所提供用户凭证和登录表单提供的用户凭证在格式上存在很大的差异。基于Remember-Me的用户名/密码信息是经过特殊编码的字符串,Acegi通过RememberMeAuthenticationProvider负责对基于Cookie的用户凭证信息进行认证。所以你必须将该认证提供者添加到authenticationManager中,如④所示,注意key属性的设置,它和 代码清单 12中的key必须相同。如果保存在数据库中的密码使用了特殊编码,则你必须为RememberMeAuthenticationProvider配置特定的密码编码器,请参考代码清单 8进行配置。
删除Remember-Me的Cookie
站点如何提供了Remember-Me的功能,就必须同时提供能让用户手工删除Cookie的功能,以便用户在某些情况下,能够删除掉Cookie。Acegi提供了一个并不是很理想的实现:在退出系统时通过配置一个LogoutHandler清除Remember-Me的Cookie。
在上一节中,我们知道SecurityContextLogoutHandler是LogoutHandler的实现类,它负责在退出系统时删除HttpSession中的SecurityContext。LogoutHandler接口的另一个实现类是TokenBasedRememberMeServices(如前所述,它同时也实现了RememberMeServices接口)。你可以在LogoutFilter中添加TokenBasedRememberMeServices,以便用户退出系统时连带清除Remember-Me。
由于TokenBasedRememberMeServices已经在前面配置好了,这里仅需要简单地将其加入到LogoutFilter的LogoutHandler列表中即可,如下所示:
代码清单 15 applicationContext-acegi-plugin.xml:清除Remember-Me的Cookie
<!----><bean id="logoutFilter"
class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/index.jsp" />
<constructor-arg>
<list>
①添加清除Remember-Me Cookie的LogoutHandler
<ref bean="rememberMeServices"/>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler" />
</list>
</constructor-arg>
<property name="filterProcessesUrl" value="/j_acegi_logout" />
</bean>
…
想象一下,这种处理方式是否合理呢?可以说不合理得近乎点荒唐——用户在登录时选择启用Remember-Me功能,就是希望在退出系统后能够在Cookie中保留用户信息,方便后续系统的访问,现在居然在退出系统后就清除掉这个Cookie。也就是说,Remember-Me Cookie仅在用户登录到用户退出系统这段时间内有效,但这段时间我们根本不需要用到这个Cookie! httpSessionContextIntegrationFilter已经很好地通过HttpSession的转存实现了在不同请求之间共享Authentication的功能。
所以Acegi提供的这种设计,笔者认为只是一个使用范例,开发者必须编写自己的实现类以提供更有意义的实现。如在用户退出系统时,允许用户通过选择的方式决定是否删除Remember-Me的Cookie,或者专门提供一个清除Remember-Me Cookie的操作链接。
小结
使用Acegi,你就可以通过配置的方式完成应用程序的身份认证。这包括对密码进行加密的认证,使用Remember-Me,退出系统后清楚Session等在身份认证时常用的各项功能。用户认证是Acegi保护应用系统的第一步,因为只能获取操作用户的身份后,才能获取用户的权限,并根据用户权限进行程序安全控制。
分享到:
相关推荐
一个使用Acegi身份认证框架的笔记 可以节省你的时间 方便快速学会使用
4. ** acegi.sql**:这个文件可能包含了用于演示的数据库脚本,用于创建用户、角色和权限相关的表,这是Acegi进行身份验证和授权的基础。 5. **AcegiExample**:这可能是项目的主要代码或配置文件,包含Acegi的配置...
Acegi的核心组件包括AuthenticationManager负责身份认证,AccessDecisionManager负责访问控制决策。SecurityContextHolder作为全局的安全上下文容器,保存了每个用户的SecurityContext,其中包含了用户信息和权限。...
Acegi 安全框架是 Spring 框架的一个扩展,用于提供高级安全性和身份验证功能。在“使用 Acegi 保护 Java 应用程序:续二”这篇博文中,作者可能深入探讨了如何将 Acegi 集成到 Java 应用程序中,以增强其安全性,...
- **身份验证(Authentication)**:Acegi 提供了多种身份验证机制,如基于密码的认证、LDAP认证等,确保只有合法用户能够访问系统资源。 - **授权(Authorization)**:Acegi 提供细粒度的权限控制,允许开发者...
在本实例中,我们将深入探讨如何使用Acegi来控制用户的权限。Acegi Security已经被Spring Security替代,但其核心思想和机制仍然适用于现代的Spring Security。 首先,我们需要理解Acegi的基础概念。Acegi的核心是`...
在本Demo中,我们将深入探讨如何使用Acegi实现Basic认证,这是一种常见的HTTP身份验证方法,适用于简单的应用场景。 Basic认证是一种客户端-服务器认证方式,用户凭据(用户名和密码)以Base64编码的形式包含在请求...
Acegi 使用 Servlet 过滤器来保护 Web 应用,通过身份认证和授权来确保系统的安全性。该框架利用了 Spring 框架的依赖注入和面向切面编程(AOP)特性,使得安全功能可以独立于应用代码,降低了安全性和业务逻辑之间的...
AceGI,全称为Acegi Security,是Java领域中一个用于Spring框架的安全组件,它提供了全面的身份验证、授权和会话管理功能。这个框架在早期的Spring版本中非常流行,为开发者构建安全的Web应用程序提供了强大的支持。...
5. **测试和调试**:进行充分的测试以确保安全配置正确无误,可以使用Acegi提供的安全测试工具来辅助调试。 随着Spring Security的出现,虽然Acegi不再维护,但其设计理念和模式在Spring Security中得到了延续和...
Acegi安全框架通过一系列关键组件来实现这些功能,同时它还具备高度的灵活性,可以与各种认证机制(如容器认证、CASSSO、X509等)进行整合,以适应不同的应用场景。 #### Acegi的关键组件 Acegi安全系统由多个关键...
**认证(Authentication)**是验证用户身份的过程,Acegi Security支持多种认证机制,包括: 1. **HTTP基本认证**:基于IETF RFC标准,通过HTTP头部传递用户名和密码。 2. **HTTP摘要认证**:同样基于IETF RFC标准...
用户尝试访问受保护的资源时,ACEGI Security会通过AuthenticationManager验证其身份。如果认证成功,系统将创建一个Authentication对象,包含用户信息和权限。如果认证失败,系统会返回一个未认证的响应。 4. **...
本文将深入探讨如何利用Acegi来增强基于Spring的应用程序的安全性,并结合给定的资源进行详细说明。 一、Acegi Security概述 Acegi Security(现为Spring Security)是一个全面且高度可配置的安全框架,它提供了...
Acegi是Spring Security的前身,它是一个用于Java企业级应用的安全框架,提供了全面的身份验证、授权和会话管理功能。这个压缩包包含了Acegi的示例代码和一个学习资源,对于初学者来说是非常宝贵的资料。 首先,让...
而Acegi Security是Spring社区早期的一个安全模块,它提供了全面的身份验证、授权和会话管理功能,为基于Spring的应用程序提供了强大的安全性支持。本实战教程将深入探讨如何将Acegi Security集成到Spring框架中,...
它使用了Spring的方式提供了安全和认证安全服务,包括使用Bean Context,拦截器和面向接口的编程方式。因此,Acegi安全系统能够轻松地适用于复杂的安全需求。 安全涉及到两个不同的概念,认证和授权。前者是关于...
当部署在Web环境中时,Acegi使用Servlet过滤器拦截HTTP请求,以执行身份认证和权限检查。这种机制确保了只有经过验证的用户才能访问特定的资源。此外,通过配置Servlet过滤器,开发人员可以轻松地集成Acegi与现有的...