在tomcat集群下利用redis实现单点登陆
场景:比如说我们要实现一个集群环境,无非是把多个项目部署到多个tomcat下,然后按照一定的算法,轮询什么的随机访问多个tomcat服务器,但是问题也会有许多,比如说,我们最开始是把登陆人的信息存放到session中,但是如果是集群的情况下,比如我第一次登陆,把信息存放到session里面,但是我第二次访问的时候,访问到了第二台服务器,第二台服务器里面没有session信息,我们还得再登陆一遍,问题显而易见,session数据共享的问题。
解决思路:我们不要把信息存放到服务器的session中,要存放到一个公共的容器中,这个容器我们采用redis,然后我们每次取用户信息都去redis里面取,这不就解决了。问题又出现了,我们知道redis是key,value的数据结构,value是json格式化的用户对象信息,key呢?key当然要不一样了,因为许多登陆用户的的信息都会存在redis里面。别忘了,每个session都会有一个sessionId,这个sessionId是服务器给我们自动生成的。最开始我们都是用的这个,而且cookie每次访问服务器也会携带这个sessionId,这不是解决了?我们用sessionId来当key,User来当value,然后把这个sessionId写到本地的cookie中,每次访问服务器都从本地的cookie中取得这个sessionId,然后有了key,不就得到value了么。
需要注意的是,既然是单点登陆,顾名思义就是我登陆了这个一级域名下的服务器,然后再登陆它下面的二级域名,三级域名都不需要登陆,那这个cookie存的时候也需要讲究一下,需要把这个cooike存放到顶级域名下,他下面的子域名也就能得到这个cookie了。比如说两个域名
a.lzh.com和b.lzh.com 要实现单点登陆,就要把cookie存放到 .lzh.com 下(注意前面的点),ok。思路有了。看代码吧!!!
代码中有许多工具类,估计提供的不全,不过如果你看懂了我上面说的,你也应该知道大概具体啥意思。因为如果把每个包含的代码都贴出来,真的是好多啊。重点是理解。我其他博客也有相关的工具类。自己找找嘛。
补充:还有每次访问服务器都要把reidis的时间重新设置半小时。这个利用过滤器来实现。很简单。
CookieUtil.java
package com.mmall.util; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Created by 敲代码的卡卡罗特 */ @Slf4j public class CookieUtil { private final static String COOKIE_DOMAIN = ".happymmall.com"; //顶级域名 private final static String COOKIE_NAME = "mmall_login_token"; //存放在本地cookie的key //根据request从cookie中读取token public static String readLoginToken(HttpServletRequest request){ Cookie[] cks = request.getCookies(); if(cks != null){ for(Cookie ck : cks){ log.info("read cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ log.info("return cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); return ck.getValue(); } } } return null; } //X:domain=".happymmall.com" //a:A.happymmall.com cookie:domain=A.happymmall.com;path="/" //b:B.happymmall.com cookie:domain=B.happymmall.com;path="/" //c:A.happymmall.com/test/cc cookie:domain=A.happymmall.com;path="/test/cc" //d:A.happymmall.com/test/dd cookie:domain=A.happymmall.com;path="/test/dd" //e:A.happymmall.com/test cookie:domain=A.happymmall.com;path="/test" //写入cookie public static void writeLoginToken(HttpServletResponse response,String token){ Cookie ck = new Cookie(COOKIE_NAME,token); ck.setDomain(COOKIE_DOMAIN); ck.setPath("/");//代表设置在根目录 ck.setHttpOnly(true); //单位是秒。 //如果这个maxage不设置的话,cookie就不会写入硬盘,而是写在内存。只在当前页面有效。 ck.setMaxAge(60 * 60 * 24 * 365);//如果是-1,代表永久 log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); response.addCookie(ck); } //删除cookie public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){ Cookie[] cks = request.getCookies(); if(cks != null){ for(Cookie ck : cks){ if(StringUtils.equals(ck.getName(),COOKIE_NAME)){ ck.setDomain(COOKIE_DOMAIN); ck.setPath("/"); ck.setMaxAge(0);//设置成0,代表删除此cookie。 log.info("del cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); response.addCookie(ck); return; } } } } }
UserController.java (用户登陆。退出方法)
/** * 用户登录 * @param username * @param password * @param session * @return */ @RequestMapping(value = "login.do",method = RequestMethod.POST) @ResponseBody public ServerResponse<User> login(String username, String password, HttpSession session, HttpServletResponse httpServletResponse){ ServerResponse<User> response = iUserService.login(username,password); if(response.isSuccess()){ // session.setAttribute(Const.CURRENT_USER,response.getData()); CookieUtil.writeLoginToken(httpServletResponse,session.getId()); RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(response.getData()),Const.RedisCacheExtime.REDIS_SESSION_EXTIME); } return response; } @RequestMapping(value = "logout.do",method = RequestMethod.GET) @ResponseBody public ServerResponse<String> logout(HttpSession session,HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse){ String loginToken = CookieUtil.readLoginToken(httpServletRequest); CookieUtil.delLoginToken(httpServletRequest,httpServletResponse); RedisShardedPoolUtil.del(loginToken); // session.removeAttribute(Const.CURRENT_USER); return ServerResponse.createBySuccess(); }
SessionExpireFilter.java (过滤器,每次访问服务器把redis的时间重置30分钟)
package com.mmall.controller.common; import com.mmall.common.Const; import com.mmall.pojo.User; import com.mmall.util.CookieUtil; import com.mmall.util.JsonUtil; import com.mmall.util.RedisShardedPoolUtil; import org.apache.commons.lang.StringUtils; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * Created by 敲代码的卡卡罗特 */ public class SessionExpireFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest; String loginToken = CookieUtil.readLoginToken(httpServletRequest); if(StringUtils.isNotEmpty(loginToken)){ //判断logintoken是否为空或者""; //如果不为空的话,符合条件,继续拿user信息 String userJsonStr = RedisShardedPoolUtil.get(loginToken); User user = JsonUtil.string2Obj(userJsonStr,User.class); if(user != null){ //如果user不为空,则重置session的时间,即调用expire命令 RedisShardedPoolUtil.expire(loginToken, Const.RedisCacheExtime.REDIS_SESSION_EXTIME); } } filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { } }
ok,代码省略许多工具类和细节,主要是思想。希望大家明白,我讲的够清楚了。