根据Remember-Me进行自动登录(4)

根据Remember-Me进行自动登录

如果用户在登录时选择了Remember-Me的功能(即勾选“5天内不用再登录”复选框),登录成功后用户名/密码的信息就保存在客户机的Cookie中。下次用户直接访问站点的安全页面时,必须有一个过滤器能够自动调用RememberMeServices#autoLogin()完成自动登录的操作,这便是通过RememberMeProcessingFilter过滤器来完成的:

代码清单 14 applicationContext-acegi-security.xml

Remember-Me自动登录



…
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,
logoutFilter,rememberMeProcessingFilter ①处理自动登录的过滤器




②-1
②-2




③-1










 

首先,我们在过滤器链中添加一个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-security.xml:清除Remember-Me的Cookie





①添加清除Remember-Me Cookie的LogoutHandler





 

想象一下,这种处理方式是否合理呢?可以说不合理得近乎点荒唐——用户在登录时选择启用Remember-Me功能,就是希望在退出系统后能够在Cookie中保留用户信息,方便后续系统的访问,现在居然在退出系统后就清除掉这个Cookie。也就是说,Remember-Me Cookie仅在用户登录到用户退出系统这段时间内有效,但这段时间我们根本不需要用到这个Cookie! httpSessionContextIntegrationFilter已经很好地通过HttpSession的转存实现了在不同请求之间共享Authentication的功能。

所以Acegi提供的这种设计,笔者认为只是一个使用范例,开发者必须编写自己的实现类以提供更有意义的实现。如在用户退出系统时,允许用户通过选择的方式决定是否删除Remember-Me的Cookie,或者专门提供一个清除Remember-Me Cookie的操作链接。

小结

使用Acegi,你就可以通过配置的方式完成应用程序的身份认证。这包括对密码进行加密的认证,使用Remember-Me,退出系统后清楚Session等在身份认证时常用的各项功能。用户认证是Acegi保护应用系统的第一步,因为只能获取操作用户的身份后,才能获取用户的权限,并根据用户权限进行程序安全控制。

你可能感兴趣的:(Spring,acegi,安全框架)