class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
ref="sessionRegistry" />
<property name="maximumSessions" value="1" />
<property name="exceptionIfMaximumExceeded" value="false" />
一个账户只对应一个session,也就是一个用户在不同浏览器登陆,后登陆的会导致前面登陆的session失效。
集群环境下,导致maximumSessions的配置失效。并不能实现预期的目标。因为session没有共享。
采用spring data redis session解决实现session共享,统一管理。
但是由于项目集成了过多的开源框架,由于版本原因,很难整合到一起。并且项目测试已经接近尾声,因此没有采用。
zookeeper监听用户session 方式,后登陆时操作对应节点,触发监听事件,使其先创建的session失效。
package com.raymon.cloudq.util;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SessionContext {
private static SessionContext instance;
private Map sessionMap;
private SessionContext() {
sessionMap = new ConcurrentHashMap();
}
public synchronized static SessionContext getInstance() {
if (instance == null) {
instance = new SessionContext();
}
return instance;
}
public void addSession(HttpSession session) {
if (session != null) {
sessionMap.put(session.getId(), session);
}
}
public void delSession(HttpSession session) {
if (session != null) {
sessionMap.remove(session.getId());
}
}
public void delSession(String sessionId) {
sessionMap.remove(sessionId);
}
public HttpSession getSession(String sessionId) {
if (sessionId == null)
return null;
return sessionMap.get(sessionId);
}
}
**
* 由于session在集群中没有实现共享,一个账户只能对应一个session
* 基于zookeeper监听的 来控制
*/
public interface ClusterSessionsCtrlService {
/**
* 设置监听
*/
void setMaximumSessionsListener();
/**
* 注册zookeeper sesssion数据
* 后登陆的用户会删除先登录的节点,触发监听,让先登陆的session失效
* @param empId
* @param httpSession
*/
void registerZookeeperSession(Integer empId,HttpSession httpSession);
/**
* session超时,删除zookeeper注册数据
* @param sessionId
*/
void deleteZookeeperSessionRegister(String sessionId);
}
package com.raymon.cloudq.service.impl;
import com.raymon.cloudq.service.ClusterSessionsCtrlService;
import com.raymon.cloudq.util.SessionContext;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.transaction.CuratorOp;
import org.apache.curator.framework.api.transaction.CuratorTransactionResult;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Service
public class ClusterSessionsCtrlServiceImpl implements ClusterSessionsCtrlService, InitializingBean {
@Value(" ${zookeeperhostName}")
private String zookeeperConnectionString;
private static String sessionPath = "/session";
private static String sessionReaPath = "/sessionrea";
protected static org.slf4j.Logger logger = LoggerFactory.getLogger(ClusterSessionsCtrlServiceImpl.class);
private CuratorFramework client = null;
@Override
public void afterPropertiesSet() throws Exception {
setMaximumSessionsListener();
}
@Override
public void setMaximumSessionsListener() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
client = CuratorFrameworkFactory.builder().connectString(zookeeperConnectionString)
.sessionTimeoutMs(8000).retryPolicy(retryPolicy).build();
client.start();
try {
Stat stat = client.checkExists().forPath(sessionPath);
if (stat == null) {
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(sessionPath);
}
stat = client.checkExists().forPath(sessionReaPath);
if (stat == null) {
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(sessionReaPath);
}
} catch (Exception e) {
e.printStackTrace();
logger.error("zookeeper创建/session失败,原因{}", e.toString());
}
PathChildrenCache cache = null;
try {
cache = new PathChildrenCache(client, sessionPath, true);
cache.start();
} catch (Exception e) {
e.printStackTrace();
logger.error(e.toString());
}
PathChildrenCacheListener cacheListener = new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
logger.info("事件类型:" + event.getType());
if( event.getData()!=null){
logger.info("节点数据:" + event.getData().getPath() + " = " + new String(event.getData().getData()));
}
if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)) {
HttpSession httpSession = SessionContext.getInstance().getSession(new String(event.getData().getData()));
if (httpSession == null) {
return;
}
httpSession.invalidate();
}
}
};
cache.getListenable().addListener(cacheListener);
}
@Override
public void deleteZookeeperSessionRegister(String sessionId) {
try {
SessionContext.getInstance().delSession(sessionId);
String empId = null;
Stat stat = client.checkExists().forPath(sessionReaPath + "/" + sessionId);
if (stat != null) {
empId = new String(client.getData().forPath(sessionReaPath + "/" + sessionId));
client.delete().forPath(sessionReaPath + "/" + sessionId);
logger.info("delete session:" + sessionReaPath + "/" + sessionId);
}
/* stat = client.checkExists().forPath(sessionPath + "/" + empId);
if (StringUtils.isNotEmpty(empId) && stat != null) {
client.delete().forPath(sessionPath + "/" + empId);
logger.info("delete session:" + sessionPath + "/" + empId);
}*/
} catch (Exception e) {
e.printStackTrace();
logger.error(e.toString());
}
}
@Override
public void registerZookeeperSession(Integer empId, HttpSession httpSession) {
try {
SessionContext.getInstance().addSession(httpSession);
Stat stat = client.checkExists().forPath(sessionPath + "/" + empId);
List operations = new ArrayList();
if (stat != null) {
CuratorOp deleteOpt = client.transactionOp().delete().forPath(sessionPath + "/" + empId);
operations.add(deleteOpt);
}
CuratorOp createSessionPathOpt = client.transactionOp().create().withMode(CreateMode.EPHEMERAL).forPath(sessionPath + "/" + empId, httpSession.getId().getBytes());
CuratorOp createSessionReaPathOpt = client.transactionOp().create().withMode(CreateMode.EPHEMERAL).forPath(sessionReaPath + "/" + httpSession.getId(), String.valueOf(empId).getBytes());
operations.add(createSessionPathOpt);
operations.add(createSessionReaPathOpt);
Collection results = client.transaction().forOperations(operations);
for (CuratorTransactionResult result : results) {
logger.info(result.getForPath() + " - " + result.getType());
}
} catch (Exception e) {
e.printStackTrace();
logger.error(e.toString());
}
}
}
/**
* session监听
*
*
*/
public class SessionListener implements HttpSessionListener, HttpSessionAttributeListener{
private SessionContext context = SessionContext.getInstance();
Logger log = LoggerFactory.getLogger(SessionListener.class);
@Override
public void attributeAdded(HttpSessionBindingEvent arg0) {
}
@Override
public void attributeRemoved(HttpSessionBindingEvent arg0) {
}
@Override
public void attributeReplaced(HttpSessionBindingEvent arg0) {
}
@Override
public void sessionCreated(HttpSessionEvent arg0) {
if(log.isDebugEnabled()) {
log.debug("创建session");
}
}
@Override
public void sessionDestroyed(HttpSessionEvent arg0) {
context.delSession(arg0.getSession());
ApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(arg0.getSession().getServletContext());
ClusterSessionsCtrlService clusterSessionsCtrlService = ctx.getBean(ClusterSessionsCtrlService.class);
clusterSessionsCtrlService.deleteZookeeperSessionRegister(arg0.getSession().getId());
}
}
clusterSessionsCtrlService.registerZookeeperSession(empId,request.getSession());