当前位置 博文首页 > 程序员石磊:spring security 在没实现session共享的集群环境下

    程序员石磊:spring security 在没实现session共享的集群环境下

    作者:[db:作者] 时间:2021-08-08 19:19

    背景

    • 项目采用阿里云负载均衡,基于cookie的会话保持。
    • 没有实现集群间的session共享。
    • 项目采用spring security 并且配置了session策略如下:
    <bean
                        class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
                        <constructor-arg ref="sessionRegistry" />
                        <property name="maximumSessions" value="1" />
                        <property name="exceptionIfMaximumExceeded" value="false" />
                    </bean>

    一个账户只对应一个session,也就是一个用户在不同浏览器登陆,后登陆的会导致前面登陆的session失效。

    问题分析

    集群环境下,导致maximumSessions的配置失效。并不能实现预期的目标。因为session没有共享。

    解决思路

    • 采用spring data redis session解决实现session共享,统一管理。
      但是由于项目集成了过多的开源框架,由于版本原因,很难整合到一起。并且项目测试已经接近尾声,因此没有采用。

    • zookeeper监听用户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<String, HttpSession> sessionMap;
    
        private SessionContext() {
            sessionMap = new ConcurrentHashMap<String, HttpSession>();
        }
    
        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);
        }
    }
    

    基于zookeeper的session控制接口

    **
     * 由于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);
    }

    session控制接口实现类

    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<CuratorOp> operations = new ArrayList<CuratorOp>();
                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<CuratorTransactionResult> 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监听

    /**
     * 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());
        }
    
    }
    

    用户登陆成功需要注册session监听

        clusterSessionsCtrlService.registerZookeeperSession(empId,request.getSession());
    cs
    下一篇:没有了