Spring Boot - репликация сеанса Hazelcast с помощью Spring Security - PullRequest
0 голосов
/ 15 апреля 2019

Я пытаюсь использовать Hazelcast Distributed Cache для репликации сеанса HTTP с Spring Boot & Spring Security, но не могу это настроить (однако простая репликация кэша работает нормально, я проверил это, установив какое-то значение в map на одномузел приложения и пытаюсь получить его на другом узле кластера.)Когда приложение запускается в кластере, после входа в систему на одном узле я не получаю объект сеанса на другом узле (я получаю сеанс из объекта реестра сеанса).

Я включил зависимости: версия hazelcast: '3.12'и hazelcast-all версия: '3.12' в файле сборки Gradle.

Ниже приведены конфигурации кода, которые я пробовал до сих пор.

Я добавил ниже bean-компонент в AppConfig.java

@Bean
    public Config hazelCastConfig(){
        Config config = new Config();
        config.setInstanceName("hazelcast-instance")
                .addMapConfig(
                        new MapConfig()
                                .setName("hazelcastConfiguration")
                                .setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
                                .setEvictionPolicy(EvictionPolicy.LRU)
                                .setTimeToLiveSeconds(-1));
        NetworkConfig networkConfig = config.getNetworkConfig();
        networkConfig.setPort(6701).setPortCount(20);
        networkConfig.setPortAutoIncrement(true);
        JoinConfig join = networkConfig.getJoin();
        join.getMulticastConfig().setEnabled(false);
        join.getTcpIpConfig()
                .addMember("localhost")
                .setEnabled(true);
        return config;
    }

@Bean
    public FilterRegistrationBean hazelcastFilter(HazelcastInstance hazelcastInstance) {
        FilterRegistrationBean registration = new FilterRegistrationBean(new SpringAwareWebFilter());
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
        registration.addUrlPatterns("/*");
        registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE);
        registration.addInitParameter("sticky-session", "false");
        registration.addInitParameter("instance-name", hazelcastInstance.getName());
        return registration;
    }

@Bean
    public ServletListenerRegistrationBean<SessionListener> hazelcastSessionListener() {
        return new ServletListenerRegistrationBean<SessionListener>(new SessionListener());
    }

Из основного класса я исключил SessionAutoConfiguration.class

@SpringBootApplication
        (exclude =
        {
                DataSourceAutoConfiguration.class,
                HibernateJpaAutoConfiguration.class,
                SessionAutoConfiguration.class
        }
        )

Ниже мой SecurityConfig.java

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * Reference of UserDetailsService service class instance.
     * @see UserDetailsService
     */
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * Reference of CustomAuthenticationSuccessHandler instance.
     * @see CustomAuthenticationSuccessHandler
     */
    @Autowired
    private CustomAuthenticationSuccessHandler authenticationSuccessHandler;

    /**
     * Reference of CustomAuthenticationEntryPoint instance.
     * @see CustomAuthenticationEntryPoint
     */
    @Autowired
    private CustomAuthenticationEntryPoint authenticationEntryPoint;

    /**
     * Reference of CustomAuthenticationFailureHandler instance.
     * @see CustomAuthenticationFailureHandler
     */
    @Autowired
    private CustomAuthenticationFailureHandler authenticationFailureHandler;

    /**
     * Reference of CustomLogoutSuccessHandler instance.
     * @see CustomLogoutSuccessHandler
     */
    @Autowired
    CustomLogoutSuccessHandler customLogoutSuccessHandler;

    /**
     * Reference of PasswordEncoder utility instance.
     * @see PasswordEncoder
     */
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private SessionRegistry sessionRegistry;

    /**
     * Method representing security configuration details, provides AuthenticationManager.
     *
     * @param auth Allows for easily building in memory authentication, LDAP authentication, JDBC based
     * authentication, adding {@link UserDetailsService}, and adding
     * AuthenticationProviders.
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }

    /**
     * Method returning a bean of AuthenticationManager which is available during application lifecycle.
     *
     * @return an instance of default AuthenticationManager.
     * @throws Exception
     */
    @Bean
    public AuthenticationManager customAuthenticationManager() throws Exception {
        return authenticationManager();
    }

    /**
     * Method returning a bean of {@link ServletContextInitializer} to register {@link EventListener}s in a Servlet
     * 3.0+ container.
     *
     * This bean can be used to register the following types of listener:
     * <ul>
     * <li>{@link ServletContextAttributeListener}</li>
     * <li>{@link ServletRequestListener}</li>
     * <li>{@link ServletRequestAttributeListener}</li>
     * <li>{@link HttpSessionAttributeListener}</li>
     * <li>{@link HttpSessionListener}</li>
     * <li>{@link ServletContextListener}</li>
     * </ul>
     *
     * @return ServletListenerRegistrationBean
     */
    @Bean
    public static ServletListenerRegistrationBean httpSessionEventPublisher() {
        return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
    }

    /**
     * Method returning a bean of custom authentication filter containing custom success and failure handlers.
     * Also sets SessionAuthenticationStrategy in filter.
     *
     * @return CustomUsernamePasswordAuthenticationFilter
     * @see CustomUsernamePasswordAuthenticationFilter
     * @throws Exception
     */
    @Bean
    public CustomUsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
        CustomUsernamePasswordAuthenticationFilter authenticationFilter
                = new CustomUsernamePasswordAuthenticationFilter(sessionRegistry);
        authenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        authenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
        authenticationFilter.setRequiresAuthenticationRequestMatcher(
                new AntPathRequestMatcher("/api/login", "POST"));
        authenticationFilter.setAuthenticationManager(customAuthenticationManager());
        authenticationFilter.setSessionAuthenticationStrategy(concurrentSession());
        return authenticationFilter;
    }

    /**
     * Method representing configuration/strategy for concurrent sessions.
     *
     * @return CompositeSessionAuthenticationStrategy A SessionAuthenticationStrategy that accepts multiple
     * SessionAuthenticationStrategy implementations to delegate to. Each
     * SessionAuthenticationStrategy is invoked in turn.
     */
    @Bean
    public CompositeSessionAuthenticationStrategy concurrentSession() {

        ConcurrentSessionControlAuthenticationStrategy concurrentAuthenticationStrategy =
                new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
        concurrentAuthenticationStrategy.setMaximumSessions(1);
        concurrentAuthenticationStrategy.setExceptionIfMaximumExceeded(false);
        List<SessionAuthenticationStrategy> delegateStrategies = new ArrayList<>();
        delegateStrategies.add(concurrentAuthenticationStrategy);
        delegateStrategies.add(new SessionFixationProtectionStrategy());
        delegateStrategies.add(new RegisterSessionAuthenticationStrategy(sessionRegistry));

        CompositeSessionAuthenticationStrategy authenticationStrategy =
                new CompositeSessionAuthenticationStrategy(delegateStrategies);
        return authenticationStrategy;
    }

    /**
     * Method returning a bean of ConcurrentSessionFilter which is available during application life-cycle.
     *
     * @return ConcurrentSessionFilter
     */
    @Bean
    ConcurrentSessionFilter concurrentSessionFilter() {
        CustomConcurrentSessionFilter concurrentSessionFilter = new CustomConcurrentSessionFilter(sessionRegistry);
        return concurrentSessionFilter;
    }

    /**
     * Method representing different types of security rules/configuration for the application.
     *
     * @param http HttpSecurity object to configure HTTP security parameters.
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        http.sessionManagement().sessionAuthenticationStrategy(concurrentSession());
        http.addFilterBefore(concurrentSessionFilter(), ConcurrentSessionFilter.class);
        http.authorizeRequests()
                .antMatchers("/api/secure/org/**",
                                        "/v2/api-docs",
                                        "/configuration/ui",
                                        "/swagger-resources",
                                        "/configuration/security",
                                        "/swagger-ui.html",
                                        "/webjars*//**//**",
                                        "/swagger-resources/configuration/ui").
                hasAnyAuthority("ADMIN").anyRequest().fullyAuthenticated()

                .antMatchers("/api/secure/dms/**").
                hasAnyAuthority("ADMIN","INTERNAL").anyRequest().fullyAuthenticated()

                    .antMatchers("/api/secure/ext/**","/api/secure/tests/**").
                hasAnyAuthority("ADMIN","INTERNAL","EXT").anyRequest().fullyAuthenticated()         
                .and()
                .addFilterBefore(
                        authenticationFilter(),
                        UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new RequestFilter(), BasicAuthenticationFilter.class)
                /*.addFilterBefore(new RequestFilter(), BasicAuthenticationFilter.class)
                .formLogin().loginPage("/api/login")
                .permitAll()
                .successHandler(authenticationSuccessHandler)
                .failureHandler(authenticationFailureHandler)
                .usernameParameter("email")
                .passwordParameter("password")
                .and()
                .httpBasic().and()*/
                .csrf().ignoringAntMatchers("/api/login","/api/auth/**","/api/secure/**")
                .csrfTokenRepository(csrfTokenRepository())
                .and()
                .logout().logoutUrl("/api/logout")
                .invalidateHttpSession(false).logoutSuccessHandler(customLogoutSuccessHandler)
                .permitAll();

//        http.logout().
//                logoutUrl("/api/auth/logout").
//                logoutSuccessHandler(customLogoutSuccessHandler);
        //http.csrf().disable();
    }


    /**
     * Method overriding/representing security configuration/rules to bypasses configured URLs.
     *
     * @param web WebSecurity object to apply rules.
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/api/auth/**","/api/application/**","/api/unsecure/**");
    }

    /**
     * This method configure global security.
     *
     * @param auth AuthenticationManagerBuilder object
     * @throws Exception
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    /**
     * This method sets CSRF header name in CSRF token repository.
     *
     * @return CsrfTokenRepository repository object
     */
    private CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-XSRF-TOKEN");
        return repository;
    }
}

Может кто-нибудь подсказать, какой набор конфигурации мне не хватаетили, может быть, кто-то может поделиться примером кода или любым ресурсом, с которым я могу правильно его настроить.

Требуется просто реплицировать сеансы, чтобы другие узлы кластера знали о существующих сеансах.

Спасибозаранее !!!

1 Ответ

1 голос
/ 15 апреля 2019

Пожалуйста, проверьте мой пример проекта здесь: https://github.com/gokhanoner/seajug-demo

Он использует Hazelcast в качестве кеша сессии, а также с помощью Spring Session, я считаю, что это то, что вам нужно, и это более простая настройка.

...