登录解决方案-单体应用-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次了
成功!!!!!!!!!!!!
本文作者:KwFruit
本文链接:https://www.cnblogs.com/mangoubiubiu/p/16102931.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步