使用session保存用户登录状态(实现单点登录)

由于是软件公司,项目用户量很小,而且是传统项目,所以用session来存储用户的登录状态。前端是移动端,我为session对象写了一个工具类,供自己用,记录一下,说不定以后还会用到。

先上session工具的代码:

package com.xxxx.utils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;

public class SessionUtil {
	private static final Long sessionMaxAge = 86400L;

	private SessionUtil() {

	}

	private static class SessionUtilInner {
		private static final SessionUtil SESSION_UTIL = new SessionUtil();
	}

	public static SessionUtil getSessionUtil() {
		return SessionUtilInner.SESSION_UTIL;
	}

	private static ConcurrentHashMap<String, HttpSession> sessionMap;
	static {
		sessionMap = new ConcurrentHashMap<>();
	}

	public static ConcurrentHashMap<String, HttpSession> getSessionMap() {
		sessionMap = cleanMap(sessionMap);
		return sessionMap;
	}

	private static synchronized ConcurrentHashMap<String, HttpSession> cleanMap(
			ConcurrentHashMap<String, HttpSession> map) {
		if (map.size() < 1) {
			return map;
		}

		Set<Entry<String, HttpSession>> entrySet = map.entrySet();
		List<String> list = new ArrayList<>();
		for (Entry<String, HttpSession> entry : entrySet) {
			// 如果session过期了,就清除掉这个session
			long max_age = sessionMaxAge * 1000L;
			long time = new Date().getTime();
			long creationTime = entry.getValue().getCreationTime();
			long sessionAge = time - creationTime;
			if (sessionAge > max_age) {
				list.add(entry.getKey());
				entry.getValue().setMaxInactiveInterval(1);
			}
		}

		if (list != null && list.size() > 0) {
			for (int i = 0; i < list.size(); i++) {
				map.remove(list.get(i));
			}
		}
		list = null;
		return map;
	}

	public static synchronized void cleanOldSession(String userInfo) {
		if (sessionMap != null && sessionMap.size() > 0) {
			Set<Entry<String, HttpSession>> entrySet = sessionMap.entrySet();
			String sessionKey = "";
			for (Entry<String, HttpSession> entry : entrySet) {
				if (StringUtils.isNotBlank(userInfo)
						&& userInfo.equals((String) entry.getValue().getAttribute("userInfo"))) {
					sessionKey = entry.getKey();
					entry.getValue().setMaxInactiveInterval(1);
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					break;
				}
			}
			if (StringUtils.isNotBlank(sessionKey)) {
				sessionMap.remove(sessionKey);
			}
		}
	}
}
工具使用单例与工厂模式,单例用的懒汉式,因为懒汉式存在线程安全问题,所有用classloader机制,用内部类方式创建单例对象来保证线程安全。

session设定的有效时间是-1,即无过期时间,完全用代码来控制session的过期。这里有个问题,就是session的过期优先级是代码>web.xml配置>容器配置(我们容器用的tomcat),但是这只是一个优先级,代码虽然设置了不过期,但是如果web.xml或者tomcat的配置文件中设置了有效期的话,还是会过期。所以需要去掉web.xml和tomcat里关于session过期的配置。

然后,我们session有效时间是一天,每次取session添加用户的时候,都清理一遍超时的session。(这一机制建立在用户量少的的基础上,如果像电商项目一时间有几千几万用户登录,那就不可取了,不过那种项目一般使用redis来存储用户登录状态,也不用自己写session工具了不是?)

还有一个清理session的方法用于实现单点登录,传入用户存在session中的信息,如果一致,说明该用户又登录了,清除掉他以前的登录信息即session对象,保证每个用户只有一个登录对象。

工具类说完了,说一下使用;

String sessionInfo = appUser.getOpenid() + "|" + appUser.getUserName();
SessionUtil.cleanOldSession(sessionInfo);
HttpSession session = request.getSession(true);
session.setAttribute("userInfo", sessionInfo);
session.setMaxInactiveInterval(Integer.parseInt(sessionInvalidTime.trim())); // 设置session
String sessionKey = System.currentTimeMillis() + "";
SessionUtil.getSessionMap().put(sessionKey, session);
appUser.setSessinKey(sessionKey);
我session对象中存的是用户的openid(微信的用户id)和用户名,key是userInfo,每次登录时,先清除旧的session,然后通过
request.getSession(true)

创建一个新的session对象,存入信息,我的sessionkey用的是当前时间毫秒数(老生常谈,如果用户多可以用其他方法保证唯一,我们不需要暂时,需要的时候最简单的方法就是用uuid去掉_后返回),存到session工具类的map里面,把sessionkey返回给前端,所有需要登录的才能访问的接口,都要带着sessionkey来,才能访问。

然后,编写一个拦截器:

package com.xxxx.interceptor;

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.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.xxxx.utils.SessionUtil;
import com.xxxx.RegisterInfo;
import com.xxxx.Constant;
import com.xxxx.JsonUtil;

public class VisitIntercepeter extends HandlerInterceptorAdapter {

	@Override
	public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
			throws Exception {
		// TODO 自动生成的方法存根

	}

	@Override
	public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
			throws Exception {
		// TODO 自动生成的方法存根

	}

	@SuppressWarnings("rawtypes")
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		HttpSession session = null;
		String sessionID = "";
		if (StringUtils.isNotBlank(request.getParameter("sessinKey"))) {
			sessionID = request.getParameter("sessinKey");
		} else if (StringUtils.isNotBlank(request.getHeader("sessinKey"))) {
			sessionID = request.getHeader("sessinKey");
		}
		if (sessionID != null) {
			session = (HttpSession) SessionUtil.getSessionMap().get(sessionID);
			if (session != null) {
				String sessionValue = (String) session.getAttribute("userInfo");
				if (StringUtils.isNotBlank(sessionValue)) {
					return true;
				}
			}
		}
		RegisterInfo registerInfo = new RegisterInfo<>();
		registerInfo.setCode(Constant.MESSAGE_ERROR_CODE);
		registerInfo.setMessage(Constant.MESSAGE_SESSION_OVERDUE);
		registerInfo.setResult(false);
		String json = JsonUtil.parseBeanToJson(registerInfo);
		response.setCharacterEncoding("UTF-8");
		response.setContentType("application/json; charset=utf-8");
		response.getWriter().write(json);
		return false;
	}
}
我们的sessionkey存在head和参数中都是可以的,用sessionkey来拦截没有登录的用户。

这样就大功告成了,项目部署的时候记得前面讲到的关于容易对session配置的问题,这个很重要。

  • 8
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值