• spring-security使用-session共享(六)


    session共享的几种方案

    方案一

    不放在服务器应用中管理,放在第三方统一管理,如redis,数据库等,现在主流都是放在redis 因为redis高效qps最高能达到10万+

    方案二

    session 拷贝,集群情况某一台服务器session发生改变,通知其他服务器,这样会有个问题,如果集群实例太多,要通知太多的服务器,而且原子性也需要保证(所有服务要么一起成功要么一起失败)

    方案三

    粘滞会话,我第一家公司就是这样处理,根据用户id nginx都路由到一个实例

    自定义

    使用spring自带的实现

    通过SpringSessionBackedSessionRegistry 通过关键字百度搜搜很多方案

    自定义SessionRegistory

    可以参考默认的org.springframework.security.core.session.SessionRegistryImpl 因为是基于应用的 sessionCRUD都是操作应用内数据结构我们

    1.接口定义

    public interface SessionRegistry {
        //获得所有会话sessionId
        List<Object> getAllPrincipals();
    
        //获得指定用户的会话列表
        List<SessionInformation> getAllSessions(Object userId, boolean var2);
    
        //获得指定sessionId的会话
        SessionInformation getSessionInformation(String sessionId);
    
        //刷新访问时间
        void refreshLastRequest(String var1);
    
        //注册会话
        void registerNewSession(String sessionId, Object var2);
    
        //删除会话
        void removeSessionInformation(String var1);
    }

    2.实现

    public class RedisSessionRegistryImpl implements SessionRegistry, ApplicationListener<SessionDestroyedEvent> {
    
        private RedisTemplate redisTemplate;
        //维护所有sessionId列表的zset score为过期时间
        private final static String SESSION_LIST_KEY = "session:list";
        //维护指定用户sessionId列表的zset score为过期时间
        private final static String SESSION_USER_LIST_KEY = "session:list:userId:%s";
        //存储sessionId对应的数据
        private final static String SESSION_ITEM_KEY = "session:item:%s";
    
        public RedisSessionRegistryImpl(RedisTemplate redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        @Override
        public void onApplicationEvent(SessionDestroyedEvent sessionDestroyedEvent) {
            String sessionId = sessionDestroyedEvent.getId();
            this.removeSessionInformation(sessionId);
        }
    
        /**
         * 获得所有会话
         *
         * @return
         */
        @Override
        public List<Object> getAllPrincipals() {
            Set<Object> sessionIds = redisTemplate.boundZSetOps(SESSION_LIST_KEY).range(0, -1);
            if (CollectionUtils.isEmpty(sessionIds)) {
                return null;
            }
            return sessionIds.stream().collect(Collectors.toList());
        }
    
        /**
         * 获得指定用户列表的会话
         *
         * @param includeExpiredSessions
         * @return
         */
        @Override
        public List<SessionInformation> getAllSessions(Object userId, boolean includeExpiredSessions) {
            Set<String> sessionsUsedByPrincipal = redisTemplate.boundZSetOps(String.format(SESSION_USER_LIST_KEY, userId)).range(0, -1);
            if (sessionsUsedByPrincipal == null) {
                return Collections.emptyList();
            } else {
                List<SessionInformation> list = new ArrayList(sessionsUsedByPrincipal.size());
                Iterator var5 = sessionsUsedByPrincipal.iterator();
    
                while (true) {
                    SessionInformation sessionInformation;
                    do {
                        do {
                            if (!var5.hasNext()) {
                                return list;
                            }
    
                            String sessionId = (String) var5.next();
                            sessionInformation = this.getSessionInformation(sessionId);
                        } while (sessionInformation == null);
                    } while (!includeExpiredSessions && sessionInformation.isExpired());
    
                    list.add(sessionInformation);
                }
            }
        }
    
        /**
         * 根据sessionId获得会话
         *
         * @param sessionId
         * @return
         */
        @Override
        public SessionInformation getSessionInformation(String sessionId) {
            Object jsonValue = redisTemplate.opsForValue().get(String.format(SESSION_ITEM_KEY, sessionId));
            if (StringUtils.isEmpty(jsonValue)) {
                return null;
            }
            JSONObject jsonObject= JSON.parseObject(jsonValue.toString());
            SessionInformation sessionInformation=new  SessionInformation(JSON.parseObject(jsonObject.getString("principal"), UserInfoDto.class),jsonObject.getString("sessionId"),jsonObject.getDate("lastRequest"));
            if(jsonObject.getBoolean("expired")){
                sessionInformation.expireNow();
            }
            return sessionInformation;
        }
    
        /**
         * 刷新session过期时间
         *
         * @param sessionId
         */
        @Override
        public void refreshLastRequest(String sessionId) {
            SessionInformation info = this.getSessionInformation(sessionId);
            if (info != null) {
                info.refreshLastRequest();
            }
            registerNewSession(sessionId, info);
        }
    
        /**
         * 注册session
         *
         * @param sessionId
         * @param principal
         */
        @Override
        public void registerNewSession(String sessionId, Object principal) {
            SessionInformation sessionInformation =new SessionInformation(principal, sessionId, new Date());
            redisTemplate.opsForValue().set(String.format(SESSION_ITEM_KEY, sessionId), JSON.toJSONString(sessionInformation));
            redisTemplate.boundZSetOps(SESSION_LIST_KEY).add(sessionId, System.currentTimeMillis() + 30000);
            String username;
            //因为第一次传入的是username 登录成功传入的是我们的UserDetails对象
            if(principal instanceof UserDetails){
                username=((UserDetails)principal).getUsername();
            }else{
                username=principal.toString();
            }
    
            redisTemplate.boundZSetOps(String.format(SESSION_ITEM_KEY, username)).add(sessionId, System.currentTimeMillis() + 30000);
    
        }
    
        /**
         * 从会话中移除
         *
         * @param sessionId
         */
        @Override
        public void removeSessionInformation(String sessionId) {
            SessionInformation sessionInformation = getSessionInformation(sessionId);
            redisTemplate.delete(String.format(SESSION_ITEM_KEY, sessionId));
            redisTemplate.boundZSetOps(SESSION_LIST_KEY).add(sessionId, System.currentTimeMillis() + 30000);
            if (sessionInformation != null) {
                redisTemplate.boundZSetOps(String.format(SESSION_ITEM_KEY, sessionInformation.getPrincipal())).add(sessionId, System.currentTimeMillis() + 30000);
            }
        }
    }

    3.替换

     @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .rememberMe()
                    .key("system")
                    .and()
                    .formLogin()
                    .authenticationDetailsSource(new MyWebAuthenticationDetailsSource())
                    .usernameParameter("loginName")
                    .passwordParameter("loginPassword")
                    .defaultSuccessUrl("/hello")
                    .failureForwardUrl("/loginFail")
                    .failureUrl("/login.html")
                    .permitAll()//不拦截
                    .and()
                    .csrf()//记得关闭
                    .disable()
                    .sessionManagement()
                    .maximumSessions(1)
                    .maxSessionsPreventsLogin(true)
                    .sessionRegistry(new RedisSessionRegistryImpl(redisTemplate));
        }

    源码

    回头看《spring-security使用-同一个账号只允许登录一次(五)》的源码

    1.org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy#onAuthentication

    public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException {
            SessionAuthenticationStrategy delegate;
            //遍历delegateStrategies 调用onAuthentication方法
            for(Iterator var4 = this.delegateStrategies.iterator(); var4.hasNext(); delegate.onAuthentication(authentication, request, response)) {
                delegate = (SessionAuthenticationStrategy)var4.next();
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Delegating to " + delegate);
                }
            }
    
        }

    2.org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy#onAuthentication

    public class RegisterSessionAuthenticationStrategy implements SessionAuthenticationStrategy {
        private final SessionRegistry sessionRegistry;
    
        public RegisterSessionAuthenticationStrategy(SessionRegistry sessionRegistry) {
            Assert.notNull(sessionRegistry, "The sessionRegistry cannot be null");
            this.sessionRegistry = sessionRegistry;
        }
    
        public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
            this.sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal());
        }
    }
  • 相关阅读:
    Filter 和 interceptor 的区别
    JAVA基础知识|Optional
    CentOS 7安装MariaDB 10详解以及相关配置
    Linux系统zookeeper环境搭建(单机、伪分布式、分布式)
    Java设计模式——模板方法模式
    Java设计模式——装饰模式
    Java设计模式——观察者模式
    Java设计模式——代理模式
    Java设计模式——适配器模式
    Java设计模式——策略模式
  • 原文地址:https://www.cnblogs.com/LQBlog/p/14245279.html
Copyright © 2020-2023  润新知