在tomcat集群下利用redis实现单点登陆

时间:2023-03-09 08:35:15
在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,代码省略许多工具类和细节,主要是思想。希望大家明白,我讲的够清楚了。