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;
}
}
As you mention above, based on the code of "SessionRepositoryFilter" class, it is clear that it supports only a single "HttpSessionIdResolver". As a result, if you use only one "SessionRepositoryFilter", your only option seems to be the one that you suggested. Although this would work, it does feel a bit hacky and also, if your requirement is indeed to use the "HTTPSessionIdResolver" for everything located under "/api**", then it doesn't ensure that.
Since "SessionRepositoryFilter" class is effectivelly a Filter, I suggest checking if you could create two Spring beans for the "SessionRepositoryFilter" class. One that will be used for all HTTP endpoints under the "/api*" pattern and another for all other paths. Then, you could use the "HttpSessionIdResolver" and "CookieSessionIdResolver" respectivelly. You can find an example for defining different filters based on URL patterns here.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With