Hi,
I am aware that this has already been discussed in #13840, but- in my opinion - this is a bug. Let me provide some details.
Describe the bug
There seems to be a difference in the configuration depending on whether the code
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
is used either in a custom DSL or in the creation of the security filter chain
To Reproduce
Please take a look at the following example:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
SecurityFilterChain restSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(a -> a
.anyRequest()
.authenticated())
// The session management is only stateless if those lines are active
// .sessionManagement(session -> session
// .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.with(PlatformDefaults.forRest(), Customizer.withDefaults())
.build();
}
@Bean
public UserDetailsService users() {
UserDetails user = User
.withDefaultPasswordEncoder()
.username("user")
.password("password")
.build();
return new InMemoryUserDetailsManager(user);
}
@RestController
@RequestMapping("/api")
static public class APIController {
@GetMapping("/")
ResponseEntity<String> getMessage() {
return ResponseEntity.ok("Hello from the API Controller");
}
}
public interface PlatformDefaults {
static PlatformDefaultsForRest forRest() {
return new PlatformDefaultsForRest();
}
final class PlatformDefaultsForRest extends AbstractHttpConfigurer<PlatformDefaultsForRest, HttpSecurity> {
@Override
public void init(HttpSecurity http) throws Exception {
http
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(Customizer.withDefaults());
}
@Override
public void configure(HttpSecurity http) throws Exception {
}
}
}
}
Notice that the stateless session management is configured in the init method as stated in the documentation ("any method that adds another configurer must be done in the init method").
Now I call the rest controller with curl:
curl --header "Authorization: Basic dXNlcjpwYXNzd29yZA==" --header "X-Requested-With: XMLHttpRequest" -i http://localhost:8080/api/
The server sets a session cookie:
Set-Cookie: JSESSIONID=DDF801BE60FD705D98001936219E04D9; Path=/; HttpOnly
If, on the other hand, I use the same lines inside the restSecurityFilterChain, the same request does not trigger a session cookie. Also, if I add a HttpSessionEventPublisher, the effect can also be seen on the server side, as the HttpSessionCreatedEvent is published if the management is only configured in the custom DSL.
Expected behavior
The session should not be created in either case. The code in both cases should result in the same configuration.
Even if this is not a bug, this is at least a very weird behaviour and should be documented. If this is - from your side - the expected behaviour, the documentation does not state clearly what a custom DSL is allowed to do and what code it must not execute.
Thank you and best regards
Nils
Comment From: marcusdacoregio
Hi, @nils-christian. Thanks for the report.
Would you mind sharing what are you trying to achieve?
If you do not want authentication to be saved in the HTTP Session, you can provide another SecurityContextRepository implementation that fits your needs, like the RequestAttributeSecurityContextRepository.
I see that that is not an ideal behavior and looks like a bug, however, we are considering deprecating the SessionManagementFilter and its configurations since all of them could be achieved by other means and the security builders are a really sensitive part to touch.
Comment From: nils-christian
Hi @marcusdacoregio ,
Sure. I need to provide various default security configurations for different use cases (stateless REST API without CSRF, stateful frontend API with CSRF, ...).
In some cases applications want to define a security chain for a frontend API under path A and another security chain for a REST API under path B. So I want to make sure that the REST API calls to path B are stateless, while the calls to path A create a session.
In the meantime we solved the issue by providing configuration methods that get the HttpSecurity (the builder) as input, so this ticket is no longer an actual issue for us, but I still think that the behaviour of the DSL is weird at this specific point.
Best regards
Nils
Comment From: marcusdacoregio
So I want to make sure that the REST API calls to path B are stateless, while the calls to path A create a session.
To avoid saving the SecurityContext in an HTTP session you can use the RequestAttributeSecurityContextRepository with the securityContext DSL.
Disabling configurers, like CSRF, is fine to do in a custom DSL, however, when we are dealing with shared objects, that becomes a little trickier and I'd rather put effort into simplifying session management via #12612 instead of trying to fix that bug which is very likely to produce side effects.
That said, I will close this issue since we do have workarounds and there is a plan to replace the SessionManagementFilter and related DSL, thus making the fix of this bug not worth the effort for now.
If someone still thinks that this can produce errors that do not have a proper workaround, we can reopen it. Thank you so much for your contribution @nils-christian.