1. session同步的需求:
比如你的代码部署了机器A和机器B,这个时候你改了代码,想重新部署,并且不让用户发觉,也就是不影响正在使用你的系统的用户正常使用
session过期的需求:
用户登录后,很长时间并未进行任何操作,应提示登录过期,否则用户看到的是未更新的数据等问题(一般过期时间2小时)
2. 应用场景:
(1)用户正在访问的是机器A,填写了一个申请单或者什么别的操作
(2)这个时候你部署机器A,导致用户没法继续访问A了,那么用户就去访问B, 这个时候用户和机器A的Session断了。
(3) 为了不影响用户正常使用,你需要把用户和机器A的session,在用户一登录的时候,就保存到cookie一份,保存到redis一份。
在用户和机器A断了session,并和B连接重新建立session的时候,你需要写代码去cookie里取一下sessionid,
然后根据这个sessionid去redis里查一下,查到的数据重新赋值给 用户和机器B建立的session,
这样你部署机器A的时候,正在访问系统的用户,之前的session得到延续,就不用重新登录系统了
(我自己的情况:部署的nginx,设置为用户请求默认访问A,A挂了的情况下访问机器B)
3. 实现:
(1) logincontroller代码中,也就是用户的登录代码中,用户的用户名和密码一验证完成,接着加入代码:
request.setAttribute(SESSION_INFO, sessionInfo);
request.getSession().setAttribute(SESSION_INFO,sessionInfo);//放到session里便于jsp页面里取用户信息
CookieHelper.setCookie(response, COOKIE_ID, encodeStrByBase64(SessionId));
redisCacheUtil.setObject(SessionId, sessionInfo);
其中的SessionId是登录完毕后,request.getSession().getId() 获取的
CookieHelper是自己写的cookie相关的方法,setCookie里用的是javax.servlet.cookie
public static void setCookie(HttpServletResponse response, String name,
String value, int maxAge) {
if (value == null)
value = "";
Cookie cookie = new Cookie(name, value);
if (maxAge != 0) {
cookie.setMaxAge(maxAge);
} else {
cookie.setMaxAge(3600); //一小时的有效时间,这里可根据自己的需要设置不同的时间长度
}
cookie.setPath("/");
response.addCookie(cookie);
}
encodeByBase64方法是加密方法,根据自己的需求选择不同的加密方法,比如md5啊,base64啊
redisCacheUtil是redis管理的方法:
/**
* 将对象存入redis中
* @param key-对象在redis里对应的key
* @param obj-对象在redis里对应key的值
* @return
*/
public String setObject(final String key,final Object obj) {
return new Executor<String>(jedisPool) {
@Override
String execute() {
String result = jedis.set(key.getBytes(), SerializeUtil.serialize(obj));
jedis.expire(key.getBytes(), 3600);
return result;
}
}.getResult();
}
(2) springmvc.XML文件中配置拦截(对大部分请求,都需要拦截看session是否有效,登录或者退出这种url就没必要拦截判断了):
<!-- 拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.xx.interceptors.SecurityInterceptor">
<!-- 不需要权限验证的地址 -->
<property name="excludeUrls">
<list>
<value>/</value> <!-- 用户登录页面 -->
<value>/login</value> <!-- 用户登录 -->
<value>/register</value> <!-- 用户注册 -->
<value>/console</value>
<value>/index</value>
<value>/logout</value>
</list>
</property>
<!-- 引入spring-redis.xml里定义的bean -->
<property name="redisCacheUtil">
<ref bean="redisCacheUtil"/>
</property>
</bean>
</mvc:interceptor>
</mvc:interceptors>
(3)在web.xml中配置session过期时间
<!-- 配置session超时时间1小时,单位分钟 . -->
<session-config>
<session-timeout>60</session-timeout>
</session-config>
(4)拦截后的url,在springmvc.xml中配置的SecurityInterceptor类的 preHandle方法,是拦截到的url必须要执行的代码,
在这里,判断用户的session是否过期或者session同步的功能支持
SessionInfo info = (SessionInfo) request.getSession().getAttribute(SESSION_INFO);
if(info != null) {
request.setAttribute(SESSION_INFO, info);//为了不改代码里面的大部分内容
//还得不停的刷一下redis,不然用户连续操作三个小时的情况下,由于redis两小时过期,会导致部署代码时导致的session中断,用户感知到
String SessionId=request.getSession().getId();
if(StringUtils.isNotBlank(SessionId)){
redisCacheUtil.setObject(SessionId, info);
}
return true;
}else {
//一旦检测到用户和服务器的服务中断(服务部署代码中断或者用户session过期中断),也就是request请求中的SessionInfo为空,
//那么先去用户浏览器的cookie中拿sessionid,
//然后根据sessionid去redis拿数据,
//如果redis的也过期了,那就说明是session正常过期
//如果redis有,说明是部署A机器的时候,session中断,那么这时候切换到B机器,session可以在B机器上继续使用
String cookieValue = CookieHelper.getCookieValue(request, COOKIE_ID);
String userCookieValue = decodeByBase64(cookieValue);//对cookie进行解码
Object obj = redisCacheUtil.getObject(userCookieValue);
if(obj!=null){
logger.error("正在部署机器,用户此时应该能session共享!");
SessionInfo sessionInfo=(SessionInfo) obj;
request.setAttribute(SESSION_INFO, sessionInfo);
request.getSession().setAttribute(Constant.SESSION_INFO,sessionInfo);//放到session里便于jsp页面里取用户信息
return true;
}else{
logger.error("您还没有登录或登录已超时,请重新登录,然后再刷新本功能!");
StringBuilder jsonStr = new StringBuilder();
jsonStr.append("{");
jsonStr.append("\"code\":\"test_login\"");
jsonStr.append(",\"ssoUrl\":\""+ConstantUtil.get("session_url") + "/error/logon.jsp\"");
jsonStr.append("}");
response.setHeader("Content-type", "text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.setStatus(403);
response.getWriter().write(jsonStr.toString());
//logger.error("test sessiontimeout url="+jsonStr.toString());
return false;
}