Spring security 使用redis作为会话存储的KeyClope反向通道注销spring会话 我使用keydove作为(单点登录/注销)功能的中央身份验证服务 我有app1,app2,app3。app1和app2是单感官应用。app3使用spring会话(使用redis作为会话存储) 所有功能都很好。但我使用后台通道注销SSO(单点注销)功能,这对app1和app2都有效。但它不适用于此app3

Spring security 使用redis作为会话存储的KeyClope反向通道注销spring会话 我使用keydove作为(单点登录/注销)功能的中央身份验证服务 我有app1,app2,app3。app1和app2是单感官应用。app3使用spring会话(使用redis作为会话存储) 所有功能都很好。但我使用后台通道注销SSO(单点注销)功能,这对app1和app2都有效。但它不适用于此app3,spring-security,redis,keycloak,spring-session,Spring Security,Redis,Keycloak,Spring Session,我想知道当客户端用户向其发送注销请求时,如何支持使用spring会话的通道注销应用程序调用KeyDope管理员url。我发现对于addKeyDoppeAuthenticatorValve,使用KeyDoppeAutoConfiguration>GetKeyDoppeContainerCustomizer()injectWebServerFactoryCustomizer,那个阀门呢 使用CatalInUserSessionManagement,但它没有任何关于redis的信息作为其会话存储。因此

我想知道当客户端用户向其发送注销请求时,如何支持使用spring会话的通道注销应用程序调用KeyDope管理员url。我发现对于addKeyDoppeAuthenticatorValve,使用KeyDoppeAutoConfiguration>GetKeyDoppeContainerCustomizer()injectWebServerFactoryCustomizer,那个阀门呢 使用CatalInUserSessionManagement,但它没有任何关于redis的信息作为其会话存储。因此,我添加了一个自定义项来增强阀门

  • 首先,我设置了自动配置的顺序,因为后面必须回调额外的定制器
  • @Slf4j
    @组成部分
    公共类BeanFactoryOrderWrapper实现DestructionAwareBeanPostProcessor{
    @凌驾
    public void PostProcessBeforeDestroyment(对象bean、字符串beanName)抛出BeanException{
    }
    @凌驾
    公共布尔要求销毁(对象bean){
    返回true;
    }
    @凌驾
    公共对象后处理BeforeInitialization(对象bean、字符串beanName)抛出BeanException{
    if(beanName.equals(“GetKeyDoveContainerCustomizer”)){
    objectwrapres=this.wrapOrder(bean);
    返回wrapRes;
    }
    返回豆;
    }
    @凌驾
    公共对象后处理初始化后(对象bean、字符串beanName)抛出BeansException{
    返回豆;
    }
    私有对象包装器(对象bean){
    log.info(“为下一个定制重写KeyClope自动配置定制器订单”);
    最终WebServerFactoryCustomizer源=(WebServerFactoryCustomizer)bean;
    返回新的keydoveecontainercustomizerWithOrder(源代码);
    }
    }
    类KeyCoverContainerCustomizerWithOrder实现WebServerFactoryCustomizer,Ordered{
    私有最终WebServerFactoryCustomizer来源;
    public KeyDopperContainerCustomizerWithOrder(WebServerFactoryCustomizer来源){
    this.origin=origin;
    }
    @凌驾
    public void自定义(ConfigurableServletWebServerFactory){
    产地:定制(工厂);
    }
    @凌驾
    public int getOrder(){
    返回顺序。最低_优先级-1;
    }
    }
    
  • 我需要额外的RedisIndexedSessionRepository,并将其设置为代理对象
  • @Slf4j
    @配置
    @所需参数构造函数
    类别容器配置{
    私有最终RedisIndexedSessionRepository会话存储库;
    @豆子
    public WebServerFactoryCustomizer GetKeyDopperContainerCustomizerGai(){
    返回configurableServletWebServerFactory->{
    if(TomcatServletWebServerFactory的可配置ServletWebServerFactory实例){
    TomcatServletWebServerFactory容器=(TomcatServletWebServerFactory)configurableServletWebServerFactory;
    container.getContextValves().stream().filter(ele->ele.getClass()==keydeportAuthenticatorValve.class).findFirst().map(ele->(abstractkeydeportAuthenticatorValve)ele).ifPresent(valve->{
    试一试{
    final Field=AbstractKeyDoppeAuthenticatorValve.class.getDeclaredField(“userSessionManagement”);
    字段。setAccessible(true);
    最终CatalInUserSessionManagement原点=(CatalInUserSessionManagement)字段.get(阀门);
    field.set(valve,新CatalInUserSessionManagementGai(origin,sessionRepository));
    }捕获(例外e){
    日志错误(“增强阀故障”);
    }
    });
    }
    };
    }
    }
    @Slf4j
    类catalinusersessionmanagementgai扩展了catalinusersessionmanagement{
    私人会议管理起源;
    私有最终RedisIndexedSessionRepository会话存储库;
    公共CatalInUserSessionManagementGAI(CatalInUserSessionManagementOrigin,RediIndexedSessionRepository sessionRepository){
    this.origin=origin;
    this.sessionRepository=sessionRepository;
    }
    公共无效登录(会话){
    登录(会话);
    }
    public void logoutAll(管理器会话管理器){
    logoutAll(会话管理器);
    }
    public void logoutHttpSessions(Manager sessionManager,List sessionid){
    for(字符串sessionId:sessionId){
    注销会话(sessionManager,sessionId);
    }
    }
    受保护的void logoutSession(管理器管理器,字符串httpSessionId){
    试一试{
    final方法Method=catalinusersessionmanagement.class.getDeclaredMethod(“logoutSession”,Manager.class,String.class);
    方法setAccessible(true);
    调用(origin、manager、httpSessionId);
    }捕获(例外e){
    log.error(“会话管理器代理调用错误”);
    }
    //增强部分
    deleteById(httpSessionId);
    }
    受保护的无效注销会话(会话){
    试一试{
    最终方法=CatalInUserSessionManagement.class.getDeclaredMethod(“logoutSession”,Session.class);
    方法setAccessible(true);
    调用(源、会话);
    }捕获(例外e){
    log.error(“会话管理器代理调用错误”);
    }
    }
    public void sessionEvent(sessionEvent事件){
    来源:sessionEvent(事件);
    }
    }
    
    那对我有用

    @Slf4j
    @Component
    public class BeanFactoryOrderWrapper implements DestructionAwareBeanPostProcessor {
        @Override
        public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
    
        }
    
        @Override
        public boolean requiresDestruction(Object bean) {
            return true;
        }
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (beanName.equals("getKeycloakContainerCustomizer")) {
                Object wrapRes = this.wrapOrder(bean);
                return wrapRes;
            }
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        private Object wrapOrder(Object bean) {
            log.info("rewrite keycloak auto config customizer Order for next custom");
            final WebServerFactoryCustomizer origin = (WebServerFactoryCustomizer) bean;
            return new KeycloakContainerCustomizerWithOrder(origin);
        }
    }
    
    class KeycloakContainerCustomizerWithOrder implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
    
        private final WebServerFactoryCustomizer origin;
    
        public KeycloakContainerCustomizerWithOrder(WebServerFactoryCustomizer origin) {
            this.origin = origin;
        }
    
        @Override
        public void customize(ConfigurableServletWebServerFactory factory) {
            origin.customize(factory);
        }
    
        @Override
        public int getOrder() {
            return Ordered.LOWEST_PRECEDENCE - 1;
        }
    }
    
    
    
    @Slf4j
    @Configuration
    @RequiredArgsConstructor
    class ContainerConfig {
        private final RedisIndexedSessionRepository sessionRepository;
    
        @Bean
        public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> getKeycloakContainerCustomizerGai() {
            return configurableServletWebServerFactory -> {
                if (configurableServletWebServerFactory instanceof TomcatServletWebServerFactory) {
                    TomcatServletWebServerFactory container = (TomcatServletWebServerFactory) configurableServletWebServerFactory;
                    container.getContextValves().stream().filter(ele -> ele.getClass() == KeycloakAuthenticatorValve.class).findFirst().map(ele -> (AbstractKeycloakAuthenticatorValve) ele).ifPresent(valve -> {
                        try {
                            final Field field = AbstractKeycloakAuthenticatorValve.class.getDeclaredField("userSessionManagement");
                            field.setAccessible(true);
                            final CatalinaUserSessionManagement origin = (CatalinaUserSessionManagement) field.get(valve);
                            field.set(valve, new CatalinaUserSessionManagementGai(origin, sessionRepository));
                        } catch (Exception e) {
                            log.error("enhence valve fail");
                        }
                    });
                }
            };
        }
    }
    
    @Slf4j
    class CatalinaUserSessionManagementGai extends CatalinaUserSessionManagement {
        private final CatalinaUserSessionManagement origin;
        private final RedisIndexedSessionRepository sessionRepository;
    
        public CatalinaUserSessionManagementGai(CatalinaUserSessionManagement origin, RedisIndexedSessionRepository sessionRepository) {
            this.origin = origin;
            this.sessionRepository = sessionRepository;
        }
    
        public void login(Session session) {
            origin.login(session);
        }
    
        public void logoutAll(Manager sessionManager) {
            origin.logoutAll(sessionManager);
        }
    
        public void logoutHttpSessions(Manager sessionManager, List<String> sessionIds) {
            for (String sessionId : sessionIds) {
                logoutSession(sessionManager, sessionId);
            }
        }
    
        protected void logoutSession(Manager manager, String httpSessionId) {
            try {
                final Method method = CatalinaUserSessionManagement.class.getDeclaredMethod("logoutSession", Manager.class, String.class);
                method.setAccessible(true);
                method.invoke(origin,manager,httpSessionId);
            } catch (Exception e) {
                log.error("session manager proxy invoke error");
            }
    
            // enhence part
            sessionRepository.deleteById(httpSessionId);
        }
    
        protected void logoutSession(Session session) {
            try {
                final Method method = CatalinaUserSessionManagement.class.getDeclaredMethod("logoutSession", Session.class);
                method.setAccessible(true);
                method.invoke(origin,session);
            } catch (Exception e) {
                log.error("session manager proxy invoke error");
            }
        }
    
        public void sessionEvent(SessionEvent event) {
            origin.sessionEvent(event);
        }
    }