登录解决方案-单体应用-Session登录(拦截器实现)-跨机房单点签退
一、需求实现
跨机房单点签退,具体业务场景,用户A在他的手机登他的账号后,你再用你的手机登录他的账号把他的账号给挤下来,什么叫做跨机房,跨机房也可以叫做跨服务器,因为Session是存在服务端的
如果生产上在不同服务器部署了同一个服务,服务A和服务B IP地址 端口不同 但是是同一个项目,A用户将他的账号登录到了A服务器,B用户将A用户的账号登录到了B服务器,怎么将A用户的账号从A服务器 挤下来,就是清掉A服务器里A用户的Session。
二、解决方案
1、用户登录存session之前,判断session这个Key存不存在(可以用Sessions.containsKey(sessionkey)来判断),存在就移除session 在存,这样 就把上一个人的session清掉了,但是这种方案不适应分布式的情况,分布式下上一个人是登录在A机的,我在B机是清不掉A机的Session的
2、跨机房单点签退
一个session就是一个会话,而会话是用JSESSIONID 取到的

在接口的请求头里面传一个用户已经登录的 JSESSIONID 服务端就能取到session

 
具体实现方案: session存的用户信息加一个判断条件 flag 每次登录时将flag设置为0 然后将用户的session信息记录到 一张session信息表中,在记录之前,先通过当前用户id查上一次用户登录的信息,然后用HTTPClient 在请求头里面塞上一次的sessionid 请求上一次的ip地址 将 fla改为1 然后拦截器 里用flag判断 当 flag里为1时 移除当前session

三、代码
1、LoginController
package com.mangoubiubiu.controller;
import com.mangoubiubiu.annotation.Auth;
import com.mangoubiubiu.entities.HuasUser;
import com.mangoubiubiu.event.SessionIdEvent;
import com.mangoubiubiu.exception.BusinessException;
import com.mangoubiubiu.exception.CommonErrorCode;
import com.mangoubiubiu.service.LoginService;
import com.mangoubiubiu.utils.R;
import com.mangoubiubiu.vo.UserLogin;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@Api(tags = "登录模快")
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class LoginController {
    final ApplicationEventPublisher context;
    final LoginService loginService;
    @ApiImplicitParam(name = "userLogin",value = "用户信息",required = true)
    @ApiOperation(value = "登录接口")
    @PostMapping("/login")
    public R login(@RequestBody UserLogin userLogin){
        boolean flag = false;
        String type = userLogin.getType();
        if(StringUtils.isBlank(type)){
            throw  new BusinessException(CommonErrorCode.E_100101);
        }
        //账号密码登录
        if("0".equals(type)){
            flag= loginService.loginByPwd(userLogin);
        //验证码登录
        }else if("1".equals(type)){
            flag= loginService.loginByPhone(userLogin);
        }
        return flag==true?R.ok():R.error().message("登录失败");
   }
    @Auth
    @ApiImplicitParam(name = "userLogin",value = "查询用户信息",required = true)
    @ApiOperation(value = "查询用户信息")
    @GetMapping("/msg")
    public R mgs(){
        HuasUser user= loginService.getUserMsg();
        return R.ok().data("list",user);
    }
    /**
     * 查询用户信息
     * @return
     */
    @ApiOperation(value = "用户退出")
    @GetMapping("/logout")
    public R logout(){
        loginService.logout();
        return R.ok();
    }
    @ApiOperation(value = "设置用户登录标识")
    @GetMapping("/setUserStatus")
    public R setUserStatus(HttpServletRequest request){
        loginService.setUserStatus(request);
        return R.ok();
    }
}
定义改变用户状态的方法

2、LoginServiceImpl
package com.mangoubiubiu.service.impl; import com.mangoubiubiu.entities.HuasUser; import com.mangoubiubiu.enums.SessionCodeEnum; import com.mangoubiubiu.event.SessionIdEvent; import com.mangoubiubiu.event.UserSingOutEvent; import com.mangoubiubiu.exception.BusinessException; import com.mangoubiubiu.exception.CommonErrorCode; import com.mangoubiubiu.mapper.HuasUserMapper; import com.mangoubiubiu.service.LoginService; import com.mangoubiubiu.service.SessionService; import com.mangoubiubiu.utils.PasswordUtil; import com.mangoubiubiu.vo.UserLogin; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @Service @RequiredArgsConstructor public class LoginServiceImpl implements LoginService { final ApplicationEventPublisher context; final HuasUserMapper mapper; final SessionService sessionService; @Override public boolean loginByPwd(UserLogin userLogin) { String account = userLogin.getAccount(); String pwd= userLogin.getPwd(); if(StringUtils.isBlank(account) || StringUtils.isBlank(pwd)){ throw new BusinessException(CommonErrorCode.E_100101); } HuasUser huasUser = mapper.selectByAccount(account); //用户不存在 if(huasUser==null){ throw new BusinessException(CommonErrorCode.U_900102); } System.out.println(huasUser.toString()); //校验密码 boolean verify = PasswordUtil.verify(pwd, huasUser.getPwd()); if(!verify){ throw new BusinessException(CommonErrorCode.U_900103); } //设置用户登录标识 huasUser.setUserLogFlag("0"); //保存session sessionService.saveSession(huasUser); //发布事件 跨机房单点签退 context.publishEvent(new UserSingOutEvent(this)); //发布事件 记录当前sessionid context.publishEvent( new SessionIdEvent(this)); return true; } @Override public boolean loginByPhone(UserLogin userLogin) { return false; } @Override public HuasUser getUserMsg() { HuasUser huasUser = (HuasUser) sessionService.getSessionByKey(SessionCodeEnum.USER_INFO.getKey()); return huasUser; } @Override public void logout() { sessionService.removieSession(); } @Override public void setUserStatus(HttpServletRequest request) { HuasUser huasUser = (HuasUser)sessionService.getSessionByKey(SessionCodeEnum.USER_INFO.getKey()); if(huasUser!=null){ huasUser.setUserLogFlag("1"); sessionService.saveSession(huasUser); } } public static void main(String[] args) { System.out.println(PasswordUtil.generate("123456")); } }

3、事件监听类
package com.mangoubiubiu.event.listener; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.mangoubiubiu.entities.HuasSessionid; import com.mangoubiubiu.entities.HuasUser; import com.mangoubiubiu.enums.SessionCodeEnum; import com.mangoubiubiu.event.SessionIdEvent; import com.mangoubiubiu.event.UserSingOutEvent; import com.mangoubiubiu.mapper.HuasSessionidMapper; import com.mangoubiubiu.service.SessionService; import com.mangoubiubiu.utils.OkHttpUtil; import com.mangoubiubiu.utils.R; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; @RequiredArgsConstructor @Slf4j @Component public class UserSingOutEventListener { final SessionService sessionService; final HuasSessionidMapper mapper; /** * 跨机房单点签退 * @param event */ @EventListener public void singOut(UserSingOutEvent event){ HuasUser user=(HuasUser)sessionService.getSessionByKey(SessionCodeEnum.USER_INFO.getKey()); HuasSessionid sessionid = mapper.selectByUserIdDesc(user.getId()); if(sessionid!=null){ String jessionid="JSESSIONID="+sessionid.getSessionId(); HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); String localPort=sessionid.getPort(); String localAddr=request.getLocalAddr(); String url="http://"+localAddr+":"+localPort+"/huas/user/setUserStatus"; JSONObject jsonObject=new JSONObject(); System.out.println("----jessionid--"+jessionid);; try { JSONObject result = OkHttpUtil.get(url, jessionid); log.info("USER SIGNOUT----->{}",result.toString()); }catch (Exception e){ log.info(e.getMessage()); } } } }

package com.mangoubiubiu.event.listener; import com.mangoubiubiu.entities.HuasSessionid; import com.mangoubiubiu.entities.HuasUser; import com.mangoubiubiu.enums.SessionCodeEnum; import com.mangoubiubiu.event.SessionIdEvent; import com.mangoubiubiu.mapper.HuasSessionidMapper; import com.mangoubiubiu.service.SessionService; import com.mangoubiubiu.utils.CodeNoUtil; import com.mangoubiubiu.utils.CodePrefixCode; import com.mangoubiubiu.utils.DateUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Date; @RequiredArgsConstructor @Slf4j @Component public class SessionIdEventListener { final SessionService sessionService; final HuasSessionidMapper mapper; @EventListener public void insertSessionId(SessionIdEvent event){ HuasUser user=(HuasUser)sessionService.getSessionByKey(SessionCodeEnum.USER_INFO.getKey()); HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); HuasSessionid huasSessionid=new HuasSessionid(); String sessionid =sessionService.getSessionId(); String userId=user.getId(); huasSessionid.setUserId(user.getId()); huasSessionid.setSessionId(sessionid); huasSessionid.setId(CodeNoUtil.getNo(CodePrefixCode.CODE_NO_PREFIX)); huasSessionid.setAddr(request.getLocalAddr()); huasSessionid.setPort(String.valueOf(request.getLocalPort())); huasSessionid.setCreateTime(DateUtil.format(new Date(),DateUtil.YYYYMMDDHHMMSS)); huasSessionid.setUpdateTime(DateUtil.format(new Date(),DateUtil.YYYYMMDDHHMMSS)); int count = mapper.selectSessionId(sessionid); if(count==0){ mapper.insert(huasSessionid); } } }

4、拦截器类
package com.mangoubiubiu.intercepter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.mangoubiubiu.entities.HuasUser; import com.mangoubiubiu.enums.SessionCodeEnum; import com.mangoubiubiu.service.SessionService; import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; @Component @RequiredArgsConstructor public class LoginInterceptor implements HandlerInterceptor { final SessionService sessionService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HuasUser user=(HuasUser)request.getSession().getAttribute(SessionCodeEnum.USER_INFO.getKey()); String servletPath = request.getServletPath(); //如果当前请求页是登录页 跳转到首页 if("/mvc/index".equals(servletPath) ){ if (user!=null) { System.out.println("/mvc/index"); request.getRequestDispatcher("/mvc/l/admin").forward(request, response); } return true; } //用户已登录 if (user!=null) { //如果用户状态改变了 移除当前session if("1".equals(user.getUserLogFlag())){ sessionService.removieSession(); } //放行 return true; } request.getRequestDispatcher("/mvc/index").forward(request, response); //重定向到登录页面 return false; } }

四、测试
先有9000 和 10000 2个服务 在9000登录后 再在10000登录 9000 刷新 后回到登录页

 
 

10000登录后拿到 sessionid 去请求 9000 让 9000去改当前用户的状态,此后 9000这边只有在有请求进入拦截器 就会把session清掉 。
刷新2次 就会退出,下图还没有请求

第一次刷新 发现没有用户信息了 并且/user/msg 返回的是一个页面

因为拦截器拦截路径是 /mvc/**
这里进入admin被拦截了 移除掉了session

        registry.addInterceptor(interceptor).addPathPatterns("/mvc/**");
再在页面请求 /user/msg 时 执行 Aop里的方法 取不到 session就跳页面

 
第二次刷新 /huas/mvc/l/admin 进入到拦截器 从session里 取不到 用户信息 就转发到了登录页

当然 登录 拦截器哪里可以直接写 移除session 后跳到登录页 就不用 刷新2次了
成功!!!!!!!!!!!!
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号