我先来说一个现象,比如已经有一个使用Spring BlazeDS Integration配置了spring security的一个应用,
如下图是用户已经登录成功时,进入的界面,此时login按钮是个摆设,没有任何功能;logout按钮是通过flex提供的api是完成登出操作:
- 当,用户没有点击logout按钮,直接关闭了浏览器,浏览器比如是firefox。
- 用户再次使用firefox打开此链接的时候,
- 呈现给用户的界面依然是登陆成功的界面。
针对于此现象,我粗浅的分析了spring security的rememberMeServices源码之后分析之后,有如下结果,
关于spring security 的 Authentication处理过程大致如下:
- “用户成功登陆之后。当用户直接关闭浏览器,再重新打开浏览器访问该web的时候”:
- org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter过滤器的doFilter()会被调用.
- doFilter()方法里面,会调用当前所配置的rememberMeServices类的autoLogin(request, response)方法
- autoLogin(request, response)方法,会检测当前浏览器所传送过来的cookie信息
- 如果cookie信息没有过期,在cookie信息里面,找到key 为 "SPRING_SECURITY_REMEMBER_ME_COOKIE" 的value (spring security所设置)
- 找到value之后,使用userService,重新检索用户信息,存放在org.springframework.security.core.userdetails.UserDetails事例对象中,
- 随后,把UserDetails,转化成Authentication,至此autoLogin(request, response)方法完成: Authentication rememberMeAuth = rememberMeServices.autoLogin(request, response);
- 继续,doFilter()方法里面,会将rememberMeAuth 放入SecurityContext中去: SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
- 前台flex Gui 就是根据是否可以从SecurityContext中拿到Authentication( Authentication authentication = SecurityContextHolder.getContext().getAuthentication();) ,去判断用户是否已经登陆了。
- 总结,“用户成功登陆之后。当用户直接关闭浏览器,再重新打开浏览器访问该web的时候”:rememberMeServices会根据cookie重新setup Authentication 到SecurityContext中。
那么session的问题来了:
- “在用户成功登陆之后。当用户直接关闭浏览器,再重新打开浏览器访问该web的时候”:
- sessionId是重新创建的,session也是新创建的,log:[BlazeDS]FlexSession created with id '81E8AFF21272ECFC84A29663D8EAAB03' for an Http-based client connection.
- 这就意味着,在浏览器关闭之前的session再也找不到了,进而之前往session所放入的value也找不到了。。。
- (注意!如果是浏览器刷新操作,一般情况,session id是不会变的)
- 总结,“在用户成功登陆之后。当用户关闭浏览器,再重新打开浏览器访问该web的时候”,session会被新建,跟cookie无关,session里面是空的,没有任何曾经设置(曾经设置,是指,在用户登录成功时候往session set的value)过的properties。
整体看下来:
- “在用户成功登陆之后。当用户直接关闭浏览器,再重新打开浏览器访问该web的时候”
- Authentication 会被“复原”:因为rememberMeServices会autoLogin()
- Session会被“新建”:因为session Id 被新建,服务器无法回到原来的session
- 因此,此时所判定的用户已经登录的状态,但是session是空的,那么如果在程序中,想从session中取一些曾经设置过的properties时候,就会报错,取不到值
解决此问题的方案1:
- 在用户登录成功的时候,将session id写入cookie
- rememberMeServices调用autoLogin()的时候,读取session id,从服务器获得原始的session
解决此问题的方案2:
- 不将session id写入 cookie
- rememberMeServices调用autoLogin()的时候,其实已经创建了新的,空的session,
- 此时,再往空session设置相关的properties
我个人感觉方案2比较好一点,因为:
- “将session id写入cookie”,这个功能实现起来不难,可以在rememberMeServices.onLoginSuccess()方法里面实现。
- 但是会引发几个问题!
- cookie是有过期时间的,session也有过期时间,其两者过期时间不相同怎么办?
- cookie在浏览器会被清除的,随之的session id也就永远消失了,原始的session再也找不到怎么办?
- java api里面并不提供通过session id获得session的方法。
网上有方法,将session可以,以key-value方式存储在servletContext里面(http://www.andowson.com/posts/list/371.page),
那么如果session 过期了,是不是还得写监听方法,将其移除? - 总之方案1,会所引发的一些列问题,太麻烦了!!!
方案2的具体实现方法:
- rememberMeServices的autoLogin()方法不允许被覆盖
- autoLogin()内部会调用processAutoLoginCookie(),此方法允许被覆盖
- 可以在自定义的rememberMeServices方法中覆盖processAutoLoginCookie(),在这里为空的session重新设置相关的properties,具体实现如下
package test;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.flex.samples.product.IProductDAO;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
public class MyRememberMeServices extends TokenBasedRememberMeServices {
private static final String CURRENT_NAME = "current_name";
@Autowired
private UserDAO userDAO;
@Override
public void onLoginSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication successfulAuthentication) {
super.onLoginSuccess(request, response, successfulAuthentication);
SecurityContextHolder.getContext().setAuthentication(
successfulAuthentication);
this.afterOnLoginSuccess(request, response, successfulAuthentication);
}
private void afterOnLoginSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication successfulAuthentication) {
HttpSession session = request.getSession();
System.out.println("login success-----------------session id = "
+ session.getId());
String userName = successfulAuthentication.getName();
//登陆成功时,为新创建出来的空session设置properties,
session.setAttribute(CURRENT_NAME, userDAO.findByUserName(userName));
}
protected UserDetails processAutoLoginCookie(String[] cookieTokens,
HttpServletRequest request, HttpServletResponse response) {
UserDetails userDetails = super.processAutoLoginCookie(cookieTokens,
request, response);
this.afterProcessAutoLoginCookie(userDetails, request, response);
return userDetails;
}
private void afterProcessAutoLoginCookie(UserDetails userDetails,
HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession(); //之前没有session,</span>在这里会根据新的session id创建新的session
System.out.println("auto login success-----------------session id = "
+ session.getId());
String userName = userDetails.getUsername();
// 当用户已经登陆,直接关闭浏览器,再次又打开浏览器,访问该web应用时候,所走的是: “自动”登陆的流程
// “自动”登陆成功时,为新创建出来的空session设置properties,
session.setAttribute(CURRENT_NAME, userDAO.findByUserName(userName));
}
}
在这里,我往session里面设置了数据库里面一整条的user记录,目的是让session里可以存储,user的id主键
我们要知道,Authentication对象里面是没有id主键的!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
security-config.xml配置文件没有什么变化:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<http entry-point-ref="entryPoint">
<anonymous enabled="false" />
<form-login login-page="/login.jsp"
authentication-success-handler-ref="simpleLoginSuccessHandler" />
<remember-me key="testdrive" services-ref="rememberMeServices" />
</http>
<beans:bean id="rememberMeServices" class="test.MyRememberMeServices">
<beans:property name="key" value="testdrive" />
<beans:property name="alwaysRemember" value="true" />
</beans:bean>
<beans:bean id="entryPoint"
class="org.springframework.flex.security3.FlexAuthenticationEntryPoint" />
<beans:bean id="simpleLoginSuccessHandler" class="test.SimpleLoginSuccessHandler">
<beans:property name="defaultTargetUrl" value="/secured/secured.html"></beans:property>
<beans:property name="forwardToDestination" value="false"></beans:property>
</beans:bean>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="john" password="john" authorities="ROLE_USER" />
<user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="guest" password="guest" authorities="ROLE_GUEST" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
ps:在此测试过程中,为什么始终使用同一个浏览器?原因是cookie,不同浏览器存储的cookie文件位置不同,比如fireFox的cookie路径:
- XP C:\Documents and Settings\用户名\Application Data\Mozilla\Firefox\Profiles\xxxxxxxx.default\
- Win7 C:\Users\用户名\AppData\Local\Mozilla\Firefox\Profiles
- 但是普通方式察看,都看不见该文件夹!