I want to use the HTTPSessionIdResolver for everything located under “/api**” and for everything else the standard CookieResolver.
How is this possible, so that the two configurations use different resolvers? With my current approach everything uses X-AUTH.
I tried to understand the implementation within Spring and I end up in the SessionRepositoryFilter, but of this filter only one instance is created, so der exists only one 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()); } } }
I could move the logic into one resolver which delegates the work to the existing resolvers, but this seems hacky?
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; } }
Advertisement
Answer
After attempting the solution provided in the question (which works fine, to be honest), I also attempted to do this by providing two different filters. However, when @EnableSpringHttpSession
is added, a SessionRepositoryFilter
is added automatically and adding two more of those in the servlet filter chain seems odd. Therefore, I thought they would have to go in the security filter chain instead, which is good because then we can use the URL matching made there as well (instead of having to implement that elsewhere as well).
Since other security filters use the HttpSession
, we have to manually place the SessionRepositoryFilter
first in this chain. This is what I came up with (in Kotlin) which works well for me:
@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) } } } }
Note that the annotation @EnableSpringHttpSession
has been removed. Instead, we add the SessionRepositoryFilter
s manually before the WebAsyncManagerIntegrationFilter
s (the first filter in the security filter chain). The function of the SessionRepositoryFilter
is to replace the existing HttpSession
with Spring’s HttpSession
which it will do no matter if we place it manually or if it’s put in place automatically by means of autoconfiguration. As long as no other filter before the security filter chain makes use of the session, this should work out. Otherwise, some filter rearrangement might still do the trick.