在Spring中使用多个HttpSessionIdResolver。

我想对位于 "api**"下的所有内容使用HTTPSessionIdResolver,对其他内容使用标准CookieResolver。

如何才能做到这一点,使两个配置使用不同的解析器?在我目前的方法中,所有的东西都使用X-AUTH。

我试图了解Spring内部的实现,我最终在SessionRepositoryFilter中,但这个过滤器只有一个实例被创建,所以der只存在一个resolver。

    @EnableWebSecurity
    public class TestConfig {

    @EnableSpringHttpSession
    @Configuration
    @Order(1)
    public static class Abc extends WebSecurityConfigurerAdapter {

        @Bean
        @Primary
        public HeaderHttpSessionIdResolver xAuth() {
            return HeaderHttpSessionIdResolver.xAuthToken();
        }

        @Bean
        @Primary
        public MapSessionRepository mapSessionRepository(){
            return new MapSessionRepository(new HashMap<>());
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/service/json/**")
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                .csrf()
                .disable();
        }

    }

    @EnableSpringHttpSession
    @Configuration
    @Order(2)
    public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {

        @ConfigurationProperties(prefix = "spring.datasource")
        @Bean
        @Primary
        public DataSource dataSource() {
            return DataSourceBuilder
                    .create()
                    .build();
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                .antMatchers("/css/**", "/user/registration", "/webfonts/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .permitAll();
        }

        @Bean
        public BCryptPasswordEncoder bcrypt() {
            return new BCryptPasswordEncoder();
        }

        @Bean
        public JdbcUserDetailsManager userDetailsManager() {
            JdbcUserDetailsManager manager = new UserDetailsManager(dataSource());
            manager.setUsersByUsernameQuery("select username,password,enabled from users where username=?");
            manager.setAuthoritiesByUsernameQuery("select username,authority from authorities where username = ?");
            return manager;
        }

        @Autowired
        public void initialize(AuthenticationManagerBuilder builder) throws Exception {
            builder.userDetailsService(userDetailsManager()).passwordEncoder(bcrypt());
        }
     }
  }

我可以将逻辑移动到一个resolver中,将工作委托给现有的resolver,但这似乎是黑客?

public class SmartHttpSessionIdResolver implements HttpSessionIdResolver {

    private static final String HEADER_X_AUTH_TOKEN = "X-Auth-Token";
    private static final CookieHttpSessionIdResolver cookie = new CookieHttpSessionIdResolver();
    private static final HeaderHttpSessionIdResolver xauth = HeaderHttpSessionIdResolver.xAuthToken();

    @Override
    public List<String> resolveSessionIds(HttpServletRequest request) {
        if (isXAuth(request)) {
            return xauth.resolveSessionIds(request);
        }
        return cookie.resolveSessionIds(request);
    }

    @Override
    public void setSessionId(HttpServletRequest request, HttpServletResponse response, String sessionId) {
        if (isXAuth(request)) {
            xauth.setSessionId(request, response, sessionId);
        } else {
            cookie.setSessionId(request, response, sessionId);
        }
    }

    @Override
    public void expireSession(HttpServletRequest request, HttpServletResponse response) {
        if (isXAuth(request)) {
            xauth.expireSession(request, response);
        } else {
            cookie.expireSession(request, response);
        }
    }

    private boolean isXAuth(HttpServletRequest request) {
        return request.getHeader(HEADER_X_AUTH_TOKEN) != null;
    }
}
2
投票

正如你上面提到的,基于""的代码。SessionRepositoryFilter"类,很明显它只支持一个 "HttpSessionIdResolver"。因此,如果你只使用一个 "SessionRepositoryFilter",你的唯一选择似乎就是你建议的那个。虽然这样做也可以,但感觉有点黑,而且,如果你的要求确实是对所有位于 "api**"下的东西都使用 "HTTPSessionIdResolver",那么也不能保证。

由于 "SessionRepositoryFilter "类实际上是一个Filter,我建议检查你是否可以为 "SessionRepositoryFilter "类创建两个Spring Bean。一个将用于 "api*"模式下的所有HTTP端点,另一个用于所有其他路径。然后,你可以分别使用 "HttpSessionIdResolver "和 "CookieSessionIdResolver"。你可以找到一个基于URL模式定义不同过滤器的例子。此处.

0
投票

在尝试了问题中提供的解决方案后(说实话,效果还不错),我也尝试通过提供两个不同的过滤器来实现。然而,当 @EnableSpringHttpSession 加了一个 SessionRepositoryFilter 是自动添加的,而在servlet过滤器链中再添加两个这样的过滤器似乎很奇怪。因此,我认为它们必须放在安全过滤器链中,这是很好的,因为这样我们也可以使用在那里做的URL匹配(而不是在其他地方也要实现)。

由于其他安全过滤器使用的是 HttpSession,我们必须手动将 SessionRepositoryFilter 链条中的第一个。这是我想出的办法(在Kotlin中),对我来说效果很好。

@EnableWebSecurity
class SecurityConfig() {

    private val sessionStore = ConcurrentHashMap<String, Session>()
    private val sessionRepo = MapSessionRepository(sessionStore)

    @Configuration
    @Order(1)
    inner class XAuthConfig(): WebSecurityConfigurerAdapter() {

        override fun configure(http: HttpSecurity) {
            http
                .requestMatchers()
                    .antMatchers("/api**")
                    .and()
                .addFilterBefore(
                    SessionRepositoryFilter(sessionRepo).apply{
                        setHttpSessionIdResolver(
                            HeaderHttpSessionIdResolver.xAuthToken();
                        )
                    }, WebAsyncManagerIntegrationFilter::class.java)
        }
    }

    @Configuration
    @Order(2)
    inner class DefaultConfig(): WebSecurityConfigurerAdapter() {

        override fun configure(http: HttpSecurity) {
            http
                .addFilterBefore(
                    SessionRepositoryFilter(sessionRepo).apply{
                        setHttpSessionIdResolver(
                            CookieHttpSessionIdResolver()
                        )
                    }, WebAsyncManagerIntegrationFilter::class.java)
            }
        }
    }

}

该注释 @EnableSpringHttpSession 已被删除。相反,我们增加了 SessionRepositoryFilter的前的手动。WebAsyncManagerIntegrationFilters(安全过滤器链中的第一个过滤器)。的功能。SessionRepositoryFilter 是为了取代现有的 HttpSession 与春天的 HttpSession 无论我们是手动放置还是通过自动配置自动放置,它都会这样做。只要在安全过滤器链之前没有其他过滤器使用会话,就应该可以解决这个问题。否则,重新安排一些过滤器可能还是会有效果的。