一、原理

1. 异地登陆

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

2. 超时登陆

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

3. 区分

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

二、实现

1. 超时登陆

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

30

2. 异地登陆

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

@RequestMapping("/loginCheck")

@ResponseBody

public Map loginCheck(HttpServletRequest request, String userLoginNumber, String userLoginPasswd) {

Map ret = new HashMap(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 map = new HashMap();

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")) {// 异地登陆时setAttribute

CommonUtil.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);

}

@Override

public 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 {

@Override

public 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;

}

@Override

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

System.out.println("postHandle");

}

@Override

public 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模式下看到,而不能直接调用判断,就是这一点比较坑,目前我在百度找的还没有什么解决办法,希望我是抛砖引玉,有大神可以解答,或者我以后找到了解决方案会随时更新。

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

  1. java用于保存登录对象怎么写,利用对象流模仿登陆注册功能——Java对象流应用...

    要求: 1.建一个User类,类中结构有用户名.密码.注册日期三个属性, 一个注册方法和一个登录方法. 2.建一个Data类,用一个List装载用户数据,有保存数据,读取数据的方法. 3.利用对象流, ...

  2. java模拟登录新版正方_java 模拟登陆正方教务系统

    比较懒,主要说一下思路.. 1.首先正方教务系统的验证码当你不去向服务器申请的时候,可以直接无视它不需要去输入验证码.可以通过浏览器屏蔽学校教务系统的图片就可以直接输入账号密码回车进去.. 2.通过抓 ...

  3. java模拟登录百度_模拟登陆百度的Java实现

    常常需要爬取百度统计出来的数据,难免要进行百度的模拟登陆!现将程序贴出来,供他人也供自己以后使用: ? ? package org.baidu; import java.util.List; impo ...

  4. java服务器登录系统,JavaWeb入门登陆注册系统

    JavaWeb入门先从登陆注册系统开始作起. 个人这个入门教程特别简单你们按步骤作就行必定没有问题的!源码在下面一个字很多你们认真仔细学 一.须要预先了解的几个基本概念,JSP文件和HTML文件还有X ...

  5. php java 单点登录_用cas来实现php的单点登陆

    最近项目中需要做单点登录,客户端包含Java.ruby和PHP,java有几个应用程序,php是discuz+supesite+ucenter,配置步骤如下: 1.cas服务端:下载地址:http:/ ...

  6. java用户登录记住密码_java项目中登陆时记住密码

    1.在登陆的时候记住密码,不知自动登陆: 2.登陆页面,填写用户名,密码,点击记住密码,下次进入登陆页面的时候,填写同样的用户名,密码自动填充(在不一次会话的情况下也就是说在不关闭浏览器的情况下): ...

  7. Java模拟登录并抓取数据

    问题: 最近做一个抓取数据的项目,发现网上很多资料不完备,或者按照代码执行不能真实爬取数据,自己特别根据自己的网站进行登录并进行数据爬取. 未登录 登录后,正常抓取数据截图(预期目标数据) 解决办法: ...

  8. Java全栈开发---Java ERP系统开发:商业ERP(七)登录与密码管理

    登录与密码管理 一.登录与退出 (一)页面的创建于修改 1.管理主界面的构建 (1)在网上找一个后台管理的模板(拷贝到webapp的目录下面) 点击这里下载: (2)访问主界面 2.登录主界面的构建 ...

  9. java wed登录面 代码_JavaWeb实现用户登录注册功能实例代码(基于Servlet+JSP+JavaBean模式)...

    下面通过通过图文并茂的方式给大家介绍JavaWeb实现用户登录注册功能实例代码,一起看看吧. 一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBea ...

最新文章

  1. 统计学 计算机论文,统计学专业论文范文
  2. Apache中KeepAlive 配置
  3. 软件工程 项目管理的目标和细节
  4. shell expect 报错 extra characters after close-brace close-quote
  5. 《Ruby程序员修炼之道》(第2版)—第1章1.2节剖析Ruby的安装
  6. NSObject中的常用方法
  7. 『机房工程』弱电必备技能培训PPT/值得您分享
  8. 关于kotlin GPUImage的使用
  9. Angular之jwt令牌身份验证
  10. mysql 主表存hash和子表的名字_【mysql】mysql分表和表分区详解
  11. 一个简单的EJB-Session Bean实例
  12. Myeclipse学习总结(16)——MyEclipse CI 2018.8.0首次更新,全新来袭!(内附破解激活文件,亲测破解100%)
  13. DeepFashion服装数据集
  14. oracle校验统一社会信用代码函数
  15. Design Compiler工具学习笔记(6)
  16. Elasticsearch ILM
  17. 【魔方攻略】五魔方教程(原创)
  18. thinkphp5常用函数汇总_(thinkPHP)PHP常用函数大全
  19. linux zip/unzip命令详解
  20. 第三天 入口文件index.php 01

热门文章

  1. Nginx-Caddy之HTTP-HTTPS代理区别
  2. IIC总线协议基本原理以及GPIO虚拟IIC接口
  3. Gym 101142G Gangsters in Central City【思维+Lca】
  4. 异质化社群量化研究4丨RATE OF CHANGE WITH BANDS
  5. 如何将源生DrawerLayout满屏显示只覆盖ActionBar
  6. 如何区分一个股票属于哪个证券交易所的,如何区分一个股票是上证指数还是深圳指数?
  7. 项目经验分享:基于昇思MindSpore实现手写汉字识别
  8. iOS开发者对苹果公司发起集体诉讼 指控其违反竞争法
  9. 后序遍历链式二叉树(递归和非递归)
  10. 每天坚持慢跑30分钟,一个月身体会有什么变化?