您的位置:首页 > 数据库 > Redis

使用Redis实现集群单点登录

2018-01-28 13:35 344 查看
       由于单点环境下,session直接存储在同一台服务下,用户登录直接获取session是没什么问题。但是在集群环境下,还是这种做法的话,由于session存储在不同服务上。假设有A和B两台服务器做成集群,它们负载均衡,如果登录请求是在A服务器下进行的,A服务下做保存session的操作,而后续的请求到B服务下,这时在B服务上获取不到对应的session(因为session是保存在A服务下),所以这时候就会提示用户未登录,这样对用户是不友好的。

    目前解决这种单点方式有很多,比如通过nginx的Ip hash,根据hash值分配用户只能请求某台服务器,但是这种做法是有缺陷的,因为根据这种计算结果,并不会均衡的分配请求。当然也可以使用spring session框架来零侵入的实现单点登录问题等。

    这里,我用的是jedis+filter+cookie+json 来原生实现单点登录。

   首先,用户登陆的时候,在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);
}

然后,将当前sessionId写入到redis中

RedisPoolUtil.setEx(session.getId(), JsonUtil.obj2String(response.getData()), Const.RedisCacheExtime.REDIS_SESSION_EXTIME);

这里redis的超时时间为1800秒即30分钟。

这样,当下一次,用户访问B服务器的时候,可以通过读取我们写入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;
}

最后通过该token,去redis获取用户的信息。整体逻辑如下:

public ServerResponse addCategory(HttpServletRequest httpServletRequest, String categoryName, @RequestParam(value = "parentId", defaultValue = "0") int parentId) {
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if (StringUtils.isEmpty(loginToken)) {
return ServerResponse.createByErroMessage("用户未登陆,无法获取到用户的个人信息");
}

String userStr = RedisPoolUtil.get(loginToken);
User user = JsonUtil.string2Obj(userStr, User.class);
if (user == null) {
return ServerResponse.createByCodeErroMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录");
}
ServerResponse checkResult = userService.checkAdmin(user);
if (!checkResult.isSuccess()) {
return ServerResponse.createByErroMessage("当前用户不是管理员,无权进行此操作");
}
return categoryService.addCategory(categoryName, parentId);
}

但是到里,我们只是设置了token的过期时间,但实际的过程中,用户一旦有活动,都需要重置token过期时间,所以需要些个拦截器来重置过期时间:

@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)) {
String userStr = RedisPoolUtil.get(loginToken);
User user = JsonUtil.string2Obj(userStr, User.class);
if (user != null) {
RedisPoolUtil.
4000
expire(loginToken, 60 * 30);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}

以上就是使用redis来解决单点登陆的问题,token值也可以使用自定义的uuid,只要有写入cookie当中就可以。

具体实现可参看个人github: https://github.com/Mrfirewind/mmall_learning
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: