站点单用户登录,后面登录上来的会把前面登录的人踢下线
公司平台接受监管后,系统整改,其中一个就是一个用户账号只能在一个地方登录,别的地方登录上来后,当前登录的人必须下线。
因为一直做web开发,之前给某公司做过这个功能,只不过那个是8年前,比较老的方式,单体系统,目前都是分布式。但是其实实现原理都是一样的。分布式系统只是部署了多份app。那么就得依赖一个第三方存储的地方,可以是db,可以是缓存。这里我就用缓存redis。
首先说一下这个单账号只能一个ip登录的原理:
明天继续。先贴代码:
LoginAloneHttpSessionListener.java
import javax.servlet.ServletContext; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.context.support.WebApplicationContextUtils; @Component @WebListener public class LoginAloneHttpSessionListener implements HttpSessionListener, HttpSessionAttributeListener { private static Logger logger = Logger.getLogger(LoginAloneHttpSessionListener.class); @Autowired private StringRedisTemplate stringRedisTemplate; /**初始化方法 stringRedisTemplate * * @param session * @author chenweixian 陈惟鲜 * @date 2018年2月26日 上午10:40:02 */ public void initStringRedisTemplate(HttpSession session){ if (stringRedisTemplate == null){ ServletContext servletContext = session.getServletContext(); ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext); stringRedisTemplate = context.getBean("stringRedisTemplate", StringRedisTemplate.class); } } @Override public void sessionCreated(HttpSessionEvent event) { this.initStringRedisTemplate(event.getSession()); // logger.info("============session【"+event.getSession().getId()+"】 已创建"); } @Override public void sessionDestroyed(HttpSessionEvent event) { this.initStringRedisTemplate(event.getSession()); BoundHashOperations<String, String, String> boundHashOperations = stringRedisTemplate.boundHashOps(RedisContants.ADMIN_LOGIN_MAP); boundHashOperations.delete(event.getSession().getId()); // logger.info("============session【"+event.getSession().getId()+"】 已销毁"); } @Override public void attributeAdded(HttpSessionBindingEvent event) { this.handleUserInfo(event); // logger.info("============session【"+event.getSession().getId()+"】====attribute Added"); } @Override public void attributeRemoved(HttpSessionBindingEvent event) { if (SystemContants.USER_INFO.equals(event.getName())){ this.initStringRedisTemplate(event.getSession()); BoundHashOperations<String, String, String> boundHashOperations = stringRedisTemplate.boundHashOps(RedisContants.ADMIN_LOGIN_MAP); boundHashOperations.delete(event.getSession().getId()); } // logger.info("============session【"+event.getSession().getId()+"】====attribute Removed"); } @Override public void attributeReplaced(HttpSessionBindingEvent event) { this.handleUserInfo(event); // logger.info("============session【"+event.getSession().getId()+"】=====attribute Replaced"); } /**处理用户信息 * * @param event * @author chenweixian 陈惟鲜 * @date 2018年2月26日 上午11:07:11 */ private void handleUserInfo(HttpSessionBindingEvent event){ if (SystemContants.USER_INFO.equals(event.getName())){ this.initStringRedisTemplate(event.getSession()); UserVo userVo = (UserVo)event.getValue(); BoundHashOperations<String, String, String> boundHashOperations = stringRedisTemplate.boundHashOps(RedisContants.ADMIN_LOGIN_MAP); // 踢出之前在线的用户 if (userVo != null){ if (boundHashOperations.keys().size() > 0){ for (String key : boundHashOperations.keys()){ if(userVo.getUserId().equalsIgnoreCase(boundHashOperations.get(key))){ // 踢出 boundHashOperations.delete(key); } } } } // 加入当前登录用户 boundHashOperations.put(event.getSession().getId(), userVo.getUserId()); } } }
LoginFilter.java
import java.io.IOException; import java.util.regex.Pattern; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.web.context.support.WebApplicationContextUtils; import com.opensymphony.oscache.util.StringUtil; /**登录过滤器 * * @author : chewneixian 陈惟鲜 * @create_date 2016年8月3日 上午11:35:23 * */ @Service @WebFilter(filterName="loginFilter", urlPatterns={ "*.do", }) public class LoginFilter implements Filter { @Autowired private DictionaryVoService dictionaryVoService; // 忽略的URL // String PASS_VALIDATION_URL = ConfigPropertiesUtil.getProp("login.ignore.passUrl"); // 日志对象 private static Logger logger = Logger.getLogger(LoginFilter.class); @Autowired private StringRedisTemplate stringRedisTemplate; public void init(FilterConfig filterConfig) throws ServletException { ServletContext servletContext = filterConfig.getServletContext(); ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext); if (stringRedisTemplate == null){ stringRedisTemplate = context.getBean("stringRedisTemplate", StringRedisTemplate.class); } if (dictionaryVoService == null){ dictionaryVoService = context.getBean("dictionaryVoService", DictionaryVoService.class); } } public void destroy() { } public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String PASS_VALIDATION_URL = dictionaryVoService.findFieldValue(DictionaryContants.TYPE_ID_PLATFORM_INFO, StatusContants.LOGIN_IGNORE_PASSURL.getIndex()); // 是否跳过检查 String requestUri = request.getRequestURI(); requestUri = requestUri.replace(request.getContextPath()+"/", ""); // 去掉工程名/ if (Pattern.matches(PASS_VALIDATION_URL,requestUri)) { chain.doFilter(req, res); return ; } // 获取登录用户信息 UserVo userVo = (UserVo)request.getSession().getAttribute(SystemContants.USER_INFO); if (userVo == null){ // 用户未登录 logger.error("您未登录,或登录超时,或您的账号在其他地方登录"); response.sendRedirect(request.getContextPath() + "/login_to.do"); return ; } if (!this.isLogined(request.getSession().getId(), userVo.getUserId())){ // 用户未登录 logger.error("您的账号已经在异地登录,请重新登录修改密码。"); response.sendRedirect(request.getContextPath() + "/login_error.do"); return ; } chain.doFilter(req, res); } /** * isLogining-用于判断用户是否已经登录 * @param sessionUserName String-登录的用户名 * @return boolean-该用户是否已经登录的标志 * */ public boolean isLogined(String nowSessionId, String nowUserId){ boolean result = false; BoundHashOperations<String, String, String> boundHashOperations = stringRedisTemplate.boundHashOps(RedisContants.ADMIN_LOGIN_MAP); if (boundHashOperations.keys().size() > 0){ // 当前登线程用户ID, 在集合中没有记录 String sessionUserId = boundHashOperations.get(nowSessionId); if (!StringUtil.isEmpty(sessionUserId)){ // 当前使用这个用户ID的线程ID String mySessionId = ""; for (String key : boundHashOperations.keys()){ if(nowUserId.equalsIgnoreCase(boundHashOperations.get(key))){ mySessionId = key; break; } } // 当前线程与登录用户线程ID相等,则登录 if (nowSessionId.equals(mySessionId)){ result = true; } } } return result; } }
标签:
单点单用户账号只能一个人登录
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?