2019独角兽企业重金招聘Python工程师标准>>>

一、原理

1. 异地登陆

    同一个账号,在不同的电脑(也可以不同的浏览器)登陆系统,前一个已经登陆的账号session被销毁,用户进行下一步操作时跳转错误页面。

2. 超时登陆

    登陆后无操作*分钟后自动销毁session,用户进行下一步操作时跳转错误页面。

3. 区分

    异地登陆和超时登陆起效时跳转的错误页面不相同。

二、实现

1. 超时登陆

由系统控制,在web.xml中配置,或者由监听器控制,利用session.setMaxInactiveInterval(interval);方法控制,本次主要展示在web.xml中配置的方法,如下:

<session-config><session-timeout>30</session-timeout><!-- 单位:分钟 -->
</session-config>

2. 异地登陆

首先在Controller中判断登陆用户的账号密码是否正确,通过以后判断session是不是异地登陆(过程在监听器中判断,此处判断监听器处理后的返回值),如果是,session销毁,如下:

@RequestMapping("/loginCheck")
@ResponseBody
public Map<String, Object> loginCheck(HttpServletRequest request, String userLoginNumber, String userLoginPasswd) {Map<String, Object> ret = new HashMap<String, Object>(2);// 存储错误信息的Map容器String errorMsg = "";// 错误信息HttpSession session = request.getSession();/*代码块,判断账号,并给errorMsg赋值或不赋值(正确)*/if (/* 账户判断成功,没有错误信息 */) {/* 监听器实现HttpSessionListener和HttpSessionAttributeListener接口。监听器private static一个Map类型的变量。当监听到对Attribute操作时(登陆验证成功向session添加用户数据,一般都会用到),进入HttpSessionAttributeListener.attributeAdded()方法,向公共map变量放入数据。当监听到session销毁操作时,进入HttpSessionListener.sessionDestroyed()方法,将公共map里的值移除。LoginListenner.isLogonUser()就是得到map中存储的值*/HttpSession isLoginSession = LoginListenner.isLogonUser(userLoginNumber);if (null != isLoginSession) {// 如果没有,则当前session是一个新的session,之前的session已经销毁// 异地登陆: 在监听器中区分超时和异地登陆, 在拦截器中判断isLoginSession.setAttribute("sessionDestroyedStatus", "busy");isLoginSession.invalidate();// 表示异地登陆,销毁session}ret.put("result", "1");}return ret;
}

监听器实现(原理在上面),包括区分异地登陆和超时登陆需要跳转不同页面的处理,过程如下:

import java.util.HashMap;
import java.util.Map;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.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import com.nielsen.sfa.common.Constants;
import com.nielsen.sfa.model.User;
import com.nielsen.sfa.utils.CommonUtil;/* @Description: 登录监听类-处理同一时间只允许账号,单地点登录*/
public class LoginListenner implements HttpSessionListener,HttpSessionAttributeListener  {/** * 用于存放账号和session对应关系的map */  private static Map<String, HttpSession> map = new HashMap<String, HttpSession>();  Logger log = LoggerFactory.getLogger(LoginListenner.class);public void sessionDestroyed(HttpSessionEvent se) {// 如果session销毁, 则从map中移除这个用户try {HttpSession session = se.getSession();System.err.println(session);
//              mobileLoginUser.remove(se.getSession());User user = (User) se.getSession().getAttribute(Constants.USER_INFO);if(null != user && StringUtils.isNotBlank(user.getUserLoginNumber()))map.remove(user.getUserLoginNumber());/*在这里做区分异地登陆和超时登陆跳转不同的错误页面的区分:超时登陆:由系统自动销毁,没有标识符异地登陆:手动销毁,销毁前用setAttribute()标识这个session销毁原因是异地登陆注意:如果是另外有手动调用session.invalidate(),也需要注明,如下手动退出然后,因为session是要销毁的,这里我们用一个公共类的公共变量Map存储标识符,然后去拦截器中判断,并控制跳转错误页面。*/// 判断session是怎么被销毁的, 并存入Map给拦截器判断(区分超时登录和异地登陆)if (null == se.getSession().getAttribute("sessionDestroyedStatus")) {// 超时自动销毁CommonUtil.sessionStatusMap.remove("sessionDestroyedStatus");CommonUtil.sessionStatusMap.put("sessionDestroyedStatus", "timeout");} else if (se.getSession().getAttribute("sessionDestroyedStatus").equals("busy")) {// 异地登陆时setAttributeCommonUtil.sessionStatusMap.remove("sessionDestroyedStatus");CommonUtil.sessionStatusMap.put("sessionDestroyedStatus", "busy");} else if (se.getSession().getAttribute("sessionDestroyedStatus").equals("logout")) {// 手动退出CommonUtil.sessionStatusMap.remove("sessionDestroyedStatus");CommonUtil.sessionStatusMap.put("sessionDestroyedStatus", "logout");}}catch (Exception e) {log.error(e.getLocalizedMessage(),e);}}/** * 当向session中放入数据触发 */  public void attributeAdded(HttpSessionBindingEvent event) {  String name = event.getName();  if (name.equals(Constants.USER_INFO)) {  User user = (User) event.getValue();
//              if (map.get(Constants.USER_INFO) != null) {
//                  HttpSession session = map.get(user.getUserLoginNumber());
//                  session.removeAttribute(user.getUserLoginNumber());
//                  session.invalidate();
//              }  map.put(user.getUserLoginNumber(), event.getSession());  }  }  /** * 当向session中移除数据触发 */  public void attributeRemoved(HttpSessionBindingEvent event) {  String name = event.getName();  if (name.equals(Constants.USER_INFO)) {  User user = (User) event.getValue();  map.remove(user.getUserLoginNumber());  }  }  public void attributeReplaced(HttpSessionBindingEvent event) {  }  public static HttpSession isLogonUser(String loginNumber) {return map.get(loginNumber);}@Overridepublic void sessionCreated(HttpSessionEvent se) {}}

然后我们在springMVC的拦截器中实现(拦截器配置这里就不写了):

import java.io.PrintWriter;import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import com.nielsen.sfa.model.User;
import com.nielsen.sfa.utils.CommonUtil;public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String contextPath = request.getServletContext().getContextPath();HttpSession session = request.getSession();User  user=(User)session.getAttribute("userInfo");/*在监听器中处理好的map,在这里判断,跳转对应的错误页面注意:跳转前需要把公共map中的值remove掉,否则会报错*/if (user == null || StringUtils.isEmpty(user.getUserLoginNumber())) {/*这里单独处理拦截AJAX请求的原因:session销毁后如用户正在进行AJAX操作中(打开AJAX页面或控件部件,只差一个提交的情况)可以正常操作*/// 拦截AJAX请求if (request.getHeader("X-Requested-With") != null && request.getHeader("X-Requested-With").equalsIgnoreCase("XMLHttpRequest")) {response.setCharacterEncoding("UTF-8");response.setContentType("text/html");// 判断session销毁的状态,设置header,在前端ajax error中判断if (null == CommonUtil.sessionStatusMap.get("sessionDestroyedStatus")|| CommonUtil.sessionStatusMap.get("sessionDestroyedStatus").equals("timeout")) {// session超时过期CommonUtil.sessionStatusMap.remove("sessionDestroyedStatus");response.setHeader("sessionStatus", "timeout");return false;} else if (CommonUtil.sessionStatusMap.get("sessionDestroyedStatus").equals("busy")) {// 异地登陆CommonUtil.sessionStatusMap.remove("sessionDestroyedStatus");response.setHeader("sessionStatus", "busy");return false;}}// "sessionDestroyedStatus"在loginController中设置if (null == CommonUtil.sessionStatusMap.get("sessionDestroyedStatus")|| CommonUtil.sessionStatusMap.get("sessionDestroyedStatus").equals("timeout")) {// session超时过期CommonUtil.sessionStatusMap.remove("sessionDestroyedStatus");String requestStatus = request.getParameter("requestStatus");/*文件上传也会拦截不了,所以手动在前端设置标识符*/if (requestStatus != null && requestStatus.equals("uploadFile")) {// 如果该请求是文件上传response.setCharacterEncoding("UTF-8");response.setContentType("text/html");PrintWriter out = null;out = response.getWriter();out.print("timeout");out.flush();} else {response.sendRedirect(contextPath + "/jsp/login.jsp");}return false;} else if (CommonUtil.sessionStatusMap.get("sessionDestroyedStatus").equals("busy")) {// 异地登陆CommonUtil.sessionStatusMap.remove("sessionDestroyedStatus");response.sendRedirect(contextPath + "/jsp/abnormal/notAuthorized.jsp");  return false;}} return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion");}}

3. 区分

这里的区分主要是跳转区分,就是根据存储在内存中的公共map标识符,在拦截器中判断跳转,代码如上。

三、建议

百度了一圈发现大家都建议对session的attribute进行操作而不建议销毁整个session,因为会遇到如下的几个坑,还有后期维护不方便,还好我写了很多注释,感觉不加注释以后别人要理解好久。

1. 说一下遇到的坑

因为session调用销毁方法,已经销毁了,你无法判断这个session有没有被销毁,null != session可不行,因为session销毁的原理是:“里面的值被清空,就是Attribute清空,然后这个session对象还在的,但是只是把isValid属性设置为false,并且对象不能被获取了,session id不变,除非你又创建了新的session”。

就是当我们要手动销毁session时,外面无法判断session有没有被销毁,用null != session判断显然是不行的,用session.isNew()和request.isRequestedSessionIDValid()判断,经测试也是不行的,当session已经被销毁,再进行session.invalidate()等对session进行操作的方法操作时,就会报错“session already invalidate”。

而最直观的isValid属性只能在DEBUG模式下看到,而不能直接调用判断,就是这一点比较坑,目前我在百度找的还没有什么解决办法,希望我是抛砖引玉,有大神可以解答,或者我以后找到了解决方案会随时更新。

转载于:https://my.oschina.net/NamiZone/blog/871542

Java-实现异地登陆和超时登陆相关推荐

  1. java 实现登录超时,Java-实现异地登陆和超时登陆

    一.原理 1. 异地登陆 同一个账号,在不同的电脑(也可以不同的浏览器)登陆系统,前一个已经登陆的账号session被销毁,用户进行下一步操作时跳转错误页面. 2. 超时登陆 登陆后无操作*分钟后自动 ...

  2. java 验证登陆_Java的登陆验证问题

    java中的登陆验证问题可以有多种方式进行验证,通过拦截器功能完成,可以通过过滤器功能完成,也可以简单的代码在JSP页面中单独完成,其中都 涉及到一个关键的验证步骤,这个验证原理ASP,PHP,JAV ...

  3. ❤️Java实现模拟QQ(消息通信+登陆界面美化)❤️

    一.登陆界面的实现 登陆界面主要使用了JFrame,以及相关的一些组件,并且在界面中加上监听 登陆界面效果图 登陆界面代码Login类 package com.lding.login;import c ...

  4. 测试案例设计-账户名登陆、QQ登陆、测试水杯

    目录 账户名和密码登陆测试 QQ登陆的测试用例 百度云盘APP核心功能需求分析 账户名和密码登陆测试 首先根据登陆这个需求的流程来分析:输入账户名.密码.验证码的测试点 输入正确的账户名.密码.验证码 ...

  5. php同个用户同时只能登陆一个, 后登陆者踢掉前登陆者

    php同个用户同时只能登陆一个, 后登陆者踢掉前登陆者php同个用户同时只能登陆一个, 后登陆者踢掉前登陆者 通常用户登陆,如果没有特别的限定, 同一个用户可以同时登陆, 今天搞了一个东西限定一个用户 ...

  6. SSH安全登陆原理:密码登陆与公钥登陆

    SSH全称(Secure SHell)是一种安全的应用层网络协议,用于计算机间的安全通信,是目前一套成熟的远程登陆解决方案. 有两种方法登陆: 密码登陆 公钥登陆 密码登陆 1.客户端填写用户名密码发 ...

  7. qq登陆inc.php,登陆验证 qq登陆验证 php 登陆验证

    用户登录验证脚本,Chkpwd.asp '=======用户登录验证脚本======= '如果尚未定义Passed对象,则将其定义为false,表示没有通过验证 If IsEmpty(Session( ...

  8. 【ubuntu20.04】安装百度输入法和搜狗输入法之后,有登陆界面,登陆之后黑屏,只有鼠标可以移动,点击无效,长时间等待提示“连接失败,请注销后重试”

    参考文章 https://blog.csdn.net/hgtjcxy/article/details/90645838 步骤如下: ctrl+alt+F4 进入命令行的界面,备注:F2-F7 自己尝试 ...

  9. joomla QQ登录,微信登录,微博登陆,人人登陆,明道登录的第三方登录

    在joomla网站上集成QQ,微信,支付宝,人人,明道,短信,微博登录现在已经成为可能.只需要安装ZMAX程序人开发的ZMAX第三方登录组件,一键就可以让你的网站轻松集成当前流行的第三方登录. 国产开 ...

  10. dede后台登陆又返回登陆界面怎么办

    dede后台登陆又返回登陆界面......网上说的DATA等文件夹都有写入权限,试过好几种方法都不行! 其实最有可能的原因就是data文件夹的权限问题.大家可以通过FTP看下data文件的权限是否是7 ...

最新文章

  1. C++中的参数传递方式:传值、传地址、传引用总结
  2. join为什么每个字符都分割了 js_js的join()与 split() (转)
  3. 273 Integer to English Words 整数转换英文表示
  4. 北京大学AI写作机器人来了,会替代记者?
  5. ServletJSP学习笔记--导航
  6. session outline for different culture
  7. 双通道和单通道区别_实测内存通道的区别:单通道比双通道内存更有优势?
  8. 2017百度之星程序设计大赛 - 复赛 01,03,05
  9. JQ 取CHECKBOX选中项值
  10. linux c代码出现段错误,Linux下段错误(C语言)
  11. 最优化学习笔记(四)共轭梯度法
  12. Windows 7 SID 修改
  13. vue-cli3+typescript+路由懒加载报错问题
  14. 企业信息管理- 近期功能改善
  15. mysql 覆盖索引 简书_mysql覆盖索引与回表
  16. 我为什么要表扬深信服(转)
  17. 采集全国疫情数据(Python)
  18. 人证核验终端设备技术
  19. 关于虚拟机闪退及无法启动的问题
  20. 12、python 海龟绘图 turtle

热门文章

  1. 星球大战 BZOJ 1015
  2. java几种常见加密算法小试
  3. oracle 自增加列的实现
  4. 判断点在多边形内部的方法(Java版)
  5. iOS 代码命名规范 及Android 代码命名规范(1)iOS
  6. 质因数分解 2012年NOIP全国联赛普及组
  7. 入门命令14-文件替换:replace
  8. 崚 不能被 iconv(gb2312,utf-8 ,string)
  9. 案例全是电商零售,B2B的用户画像怎么做???
  10. 这个爬虫是你五一假期所需要的!