Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stateless SessionPolicy not applied to SecurityFilterChain when used with CustomDsl #13840

Closed
bwgjoseph opened this issue Sep 16, 2023 · 6 comments
Assignees
Labels
for: stackoverflow A question that's better suited to stackoverflow.com

Comments

@bwgjoseph
Copy link

Describe the bug

I can't be 100% sure, but it seems to be a bug or misconfiguration to me. I can work to try to have a reproduce if required.

I suspect that somehow if I configure sessionManagement in using CustomDsl and applied to any SecurityFilterChain, it does not seem to have any effect.

So what happens was that when I try to run any HTTP request, it seems to use HttpSession cache, rather than re-authenticating on per request.

My understanding on certain concept might be wrong, if so, please let me know so I can correct it, in case any of my description of the issue is using the wrong terminology

As this is developed on airgap machine, I can't have all the logs but I will provide as much as I can, and more if required.

To Reproduce

Given the following

// project a
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity(debug = true)
public class WebSecurityConfig {
    @Bean
    public SecurityFilterChain docsFilterChain(HttpSecurity http) throws Exception {
        return http
            .build();
    }
}

// project b
public class DummyDsl extends AbstractHttpConfigurer<DummyDsl, HttpSecurity> {
    @Override
    public void init(HttpSecurity http) throws Exception {
        http
			.formLogin(AbstractHttpConfigurer::disable)
			.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
    }

    public static DummyDsl dummyDsl() {
        return new DummyDsl();
    }
}

I have exported DummyDsl through spring.factories which is used in project-a, hence, all the configuration should apply, and it does (as far as I can tell for all except session). When I try any HTTP request, the logs looks like

since I can't copy the logs, I try to hand-type the key information

This is the logs when "bypassing" the authentication

FilterChainProxy: Securing POST /api/v1/....
FilterChainProxy: Invoking DisableEncodeUrlFilter (1/11)
... omitted
FilterChainProxy: Invoking RequestHeaderAuthenticationFilter(6/11)
HttpSessionSecurityContextRepository: Retrieved SecurityContextImpl [Authentication....] from SPRING_SECURITY_CONTEXT
RequestHeaderAuthenticationFilter: Did not authenticate since request did not match ...
FilterChainProxy: Invoking RequestCacheAwareFilter (7/11)
HttpSessionRequestCache: matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
... omitted

So the request went through, using the previous session? I tried to change my headers, but it didn't also seem to re-authenticate.

What I have tried

Based on the docs, I could provide a NullSecurityContextRepository even though I think I don't have to cause I already defined using STATELESS

public class DummyDsl extends AbstractHttpConfigurer<DummyDsl, HttpSecurity> {
    @Override
    public void init(HttpSecurity http) throws Exception {
        http
			.formLogin(AbstractHttpConfigurer::disable)
			.securityContext(context -> context.securityContextRepository(new NullSecurityContextRepository())
			.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
    }

    public static DummyDsl dummyDsl() {
        return new DummyDsl();
    }
}

But, after setting this, it works. Now, every request does re-authenticate and doesn't use HttpSession (it seem).

What I have also tried

Re-define STATELESS policy in project-a, and did not set securityContext

// project a
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity(debug = true)
public class WebSecurityConfig {
    @Bean
    public SecurityFilterChain docsFilterChain(HttpSecurity http) throws Exception {
        return http
			.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .build();
    }
}

// project b
public class DummyDsl extends AbstractHttpConfigurer<DummyDsl, HttpSecurity> {
    @Override
    public void init(HttpSecurity http) throws Exception {
        http
			.formLogin(AbstractHttpConfigurer::disable)
			.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
    }

    public static DummyDsl dummyDsl() {
        return new DummyDsl();
    }
}

This also seem to work based on what I have tried

I also wanted to try

I wanted to try to setAllowSessionCreation to false (over using NullSecurityContextRepository), but not quite sure how to define it.

Expected behavior

The expected behavior should always authenticate per request, and the logs I expect to see should be something along

FilterChainProxy: Securing POST /api/v1/....
FilterChainProxy: Invoking DisableEncodeUrlFilter (1/11)
... omitted
FilterChainProxy: Invoking RequestHeaderAuthenticationFilter(6/11)
HttpSessionSecurityContextRepository: No HttpSession currently exists
SupplierDeferredSecurityContext: Created SecurityContextImpl [Null authentication]
SupplierDeferredSecurityContext: Created SecurityContextImpl [Null authentication]
RequestHeaderAuthenticationFilter: Authenticating null
RequestHeaderAuthenticationFilter: preAuthenticatePrincipal = xxxx, trying to authenticate
ProviderManager: Authenticating request with PreAuthenticatedAuthenticationProvider (1/1)
... omitted

Sample

I can try to submit a reproduce if required. I'm hoping that it's a rather straight-forward case of me not understanding enough, or having misconfiguration on my end than it is a bug. Please do let me know if more information is required as well.

Thanks!

@bwgjoseph bwgjoseph added status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Sep 16, 2023
@sjohnr
Copy link
Member

sjohnr commented Sep 28, 2023

@bwgjoseph thanks for the report. However, it's somewhat difficult to tell whether your report is simply a mis-configuration, a misunderstanding, or a bug. The formLogin() authentication mechanism is designed to work with sessions, so it's not clear how you intended to make this work with "stateless" sessionManagement().

For that reason, it feels like this is a question that would be better suited to Stack Overflow. We prefer to use GitHub issues only for bugs and enhancements. Feel free to update this issue with a link to the re-posted question (so that other people can find it) or add a minimal sample that reproduces this issue if you feel this is a genuine bug.

Having said that, please see this comment for some context. I'm going to close this issue for now. However, if you can provide a sample that points to an issue with configuring a custom DSL specifically (and not a mis-configuration), we can re-open.

@sjohnr sjohnr closed this as completed Sep 28, 2023
@sjohnr sjohnr self-assigned this Sep 28, 2023
@sjohnr sjohnr added for: stackoverflow A question that's better suited to stackoverflow.com and removed status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Sep 28, 2023
@bwgjoseph
Copy link
Author

bwgjoseph commented Sep 30, 2023

Hi @sjohnr, thanks for the response.

I do believe it is a bug, so I took some time to make the reproduce repo. As I am seeing differing behavior when used (stateless session management) with and without custom dsl.

Also, I'm not sure if I fully understand (as I'm still new to spring-security) what you explained in here, and I do think it might be a different issue.

But I like to say that this has nothing to do with formLogin. I'm not using formLogin for anything. My use-case is similar to what was described in the docs, where I want to re-authenticate on every single request without having any session stored.


In my custom dsl, I have the following configuration

public class DummyDsl extends AbstractHttpConfigurer<DummyDsl, HttpSecurity> {
    @Override
    public void init(HttpSecurity http) throws Exception {
        http
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
    }

    public static DummyDsl dummyDsl() {
        return new DummyDsl();
    }

    public HttpSecurity build() {
        return super.getBuilder();
    }
}

Which is configured in spring.factories so it will be picked up and applied automatically. So with the following configuration

public class WebSecurityConfig {
    @Bean
    public SecurityFilterChain apiFilterChain(HttpSecurity http, RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter) throws Exception {
        return http
            .addFilter(requestHeaderAuthenticationFilter)
            .build();
    }
}

I do expect that the configuration for sessionManagement will be applied apiFilterChain. However, from what I have seen, it does not get applied.


Scenario 1

When I run using the above configuration, this is the logs

curl --location 'localhost:8080/me'
--header 'X-User: hello' \

click to view logs
2023-10-01T04:16:59.149+08:00 TRACE 2816 --- [nio-9999-exec-2] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (6/12)
2023-10-01T04:16:59.151+08:00 TRACE 2816 --- [nio-9999-exec-2] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Ant [pattern='/logout', POST]
2023-10-01T04:16:59.153+08:00 TRACE 2816 --- [nio-9999-exec-2] o.s.security.web.FilterChainProxy        : Invoking RequestHeaderAuthenticationFilter (7/12)   
2023-10-01T04:16:59.156+08:00 TRACE 2816 --- [nio-9999-exec-2] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2023-10-01T04:16:59.157+08:00 TRACE 2816 --- [nio-9999-exec-2] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]   
2023-10-01T04:16:59.157+08:00 TRACE 2816 --- [nio-9999-exec-2] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]   
2023-10-01T04:16:59.158+08:00 DEBUG 2816 --- [nio-9999-exec-2] .w.a.p.RequestHeaderAuthenticationFilter : Authenticating null
2023-10-01T04:16:59.167+08:00 DEBUG 2816 --- [nio-9999-exec-2] .w.a.p.RequestHeaderAuthenticationFilter : preAuthenticatedPrincipal = hello, trying to authenticate
2023-10-01T04:16:59.173+08:00 TRACE 2816 --- [nio-9999-exec-2] o.s.s.authentication.ProviderManager     : Authenticating request with MyPreAuthenticationProvider (1/1)
2023-10-01T04:16:59.185+08:00 DEBUG 2816 --- [nio-9999-exec-2] .w.a.p.RequestHeaderAuthenticationFilter : Authentication success: PreAuthenticatedAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=bwgjoseph, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[RW]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[RW]]
2023-10-01T04:16:59.334+08:00  WARN 2816 --- [nio-9999-exec-2] o.a.c.util.SessionIdGeneratorBase        : Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [128] milliseconds.
2023-10-01T04:16:59.338+08:00  INFO 2816 --- [nio-9999-exec-2] Spring Security Debugger                 : 

************************************************************

New HTTP session created: 4A6F1D62CC3544616E31A973EBB17B45

Call stack:
// omitted
2023-10-01T04:16:59.352+08:00 DEBUG 2816 --- [nio-9999-exec-2] w.c.HttpSessionSecurityContextRepository : Stored SecurityContextImpl [Authentication=PreAuthenticatedAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=bwgjoseph, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[RW]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[RW]]] to HttpSession [org.apache.catalina.session.StandardSessionFacade@252dff08]
2023-10-01T04:16:59.357+08:00 TRACE 2816 --- [nio-9999-exec-2] o.s.security.web.FilterChainProxy        : Invoking RequestCacheAwareFilter (8/12)
// omitted

It's the first time running, so it is attempting authentication, then it stores into SecurityContextImpl

If I were to run the same request again

// omitted
2023-10-01T04:19:41.185+08:00 TRACE 2816 --- [nio-9999-exec-6] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (6/12)
2023-10-01T04:19:41.195+08:00 TRACE 2816 --- [nio-9999-exec-6] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Ant [pattern='/logout', POST]
2023-10-01T04:19:41.205+08:00 TRACE 2816 --- [nio-9999-exec-6] o.s.security.web.FilterChainProxy        : Invoking RequestHeaderAuthenticationFilter (7/12)
2023-10-01T04:19:41.209+08:00 TRACE 2816 --- [nio-9999-exec-6] w.c.HttpSessionSecurityContextRepository : Retrieved SecurityContextImpl [Authentication=PreAuthenticatedAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=bwgjoseph, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[RW]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[RW]]] from SPRING_SECURITY_CONTEXT
2023-10-01T04:19:41.211+08:00 TRACE 2816 --- [nio-9999-exec-6] .w.a.p.RequestHeaderAuthenticationFilter : Did not authenticate since request did not match [org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter$PreAuthenticatedProcessingRequestMatcher@4909428]
2023-10-01T04:19:41.212+08:00 TRACE 2816 --- [nio-9999-exec-6] o.s.security.web.FilterChainProxy        : Invoking RequestCacheAwareFilter (8/12)
2023-10-01T04:19:41.218+08:00 TRACE 2816 --- [nio-9999-exec-6] o.s.s.w.s.HttpSessionRequestCache        : matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
2023-10-01T04:19:41.246+08:00 TRACE 2816 --- [nio-9999-exec-6] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderAwareRequestFilter (9/12)
// omitted

Can see that it's not authenticating again, but retrieved from the context.

Scenario 2

The only delta here is that I am also configuring the sessionManagement directly in apiFilterChain (on top of what was already configured in dummyDsl)

public class WebSecurityConfig {
    @Bean
    public SecurityFilterChain apiFilterChain(HttpSecurity http, RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter) throws Exception {
        return http
            .addFilter(requestHeaderAuthenticationFilter)
            // i'm expecting that i should not need to add this line as it is configured in custom dsl
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .build();
    }
}

With the above configuration, I ran the same http call

click to view logs
2023-10-01T04:23:26.318+08:00 TRACE 18288 --- [nio-9999-exec-2] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (6/12)
2023-10-01T04:23:26.318+08:00 TRACE 18288 --- [nio-9999-exec-2] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Ant [pattern='/logout', POST]
2023-10-01T04:23:26.319+08:00 TRACE 18288 --- [nio-9999-exec-2] o.s.security.web.FilterChainProxy        : Invoking RequestHeaderAuthenticationFilter (7/12)  
2023-10-01T04:23:26.320+08:00 TRACE 18288 --- [nio-9999-exec-2] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]  
2023-10-01T04:23:26.321+08:00 DEBUG 18288 --- [nio-9999-exec-2] .w.a.p.RequestHeaderAuthenticationFilter : Authenticating null
2023-10-01T04:23:26.321+08:00 DEBUG 18288 --- [nio-9999-exec-2] .w.a.p.RequestHeaderAuthenticationFilter : preAuthenticatedPrincipal = hello, trying to authenticate
2023-10-01T04:23:26.323+08:00 TRACE 18288 --- [nio-9999-exec-2] o.s.s.authentication.ProviderManager     : Authenticating request with MyPreAuthenticationProvider (1/1)
2023-10-01T04:23:26.325+08:00 DEBUG 18288 --- [nio-9999-exec-2] .w.a.p.RequestHeaderAuthenticationFilter : Authentication success: PreAuthenticatedAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=bwgjoseph, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[RW]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[RW]]
2023-10-01T04:23:26.554+08:00  WARN 18288 --- [nio-9999-exec-2] o.a.c.util.SessionIdGeneratorBase        : Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [218] milliseconds.
2023-10-01T04:23:26.559+08:00  INFO 18288 --- [nio-9999-exec-2] Spring Security Debugger                 : 

************************************************************

New HTTP session created: 88E2161FD4E90002135A8B0F1C2E5897

Call stack:
// omitted
2023-10-01T04:23:26.575+08:00 DEBUG 18288 --- [nio-9999-exec-2] w.c.HttpSessionSecurityContextRepository : Stored SecurityContextImpl [Authentication=PreAuthenticatedAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=bwgjoseph, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[RW]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[RW]]] to HttpSession [org.apache.catalina.session.StandardSessionFacade@78062cf4]
2023-10-01T04:23:26.585+08:00 TRACE 18288 --- [nio-9999-exec-2] o.s.security.web.FilterChainProxy        : Invoking RequestCacheAwareFilter (8/12)
2023-10-01T04:23:26.586+08:00 TRACE 18288 --- [nio-9999-exec-2] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderAwareRequestFilter (9/12)
2023-10-01T04:23:26.589+08:00 TRACE 18288 --- [nio-9999-exec-2] o.s.security.web.FilterChainProxy        : Invoking AnonymousAuthenticationFilter (10/12)     
// omitted

So this authenticates, and also stores into context which seem correct to me

But, if I were to run the http call again. It will attempt to authenticate again, instead of retrieving from the store.

2023-10-01T04:24:35.685+08:00 TRACE 18288 --- [nio-9999-exec-5] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (6/12)
2023-10-01T04:24:35.685+08:00 TRACE 18288 --- [nio-9999-exec-5] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Ant [pattern='/logout', POST]
2023-10-01T04:24:35.685+08:00 TRACE 18288 --- [nio-9999-exec-5] o.s.security.web.FilterChainProxy        : Invoking RequestHeaderAuthenticationFilter (7/12)  
2023-10-01T04:24:35.686+08:00 TRACE 18288 --- [nio-9999-exec-5] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]  
2023-10-01T04:24:35.687+08:00 DEBUG 18288 --- [nio-9999-exec-5] .w.a.p.RequestHeaderAuthenticationFilter : Authenticating null
2023-10-01T04:24:35.687+08:00 DEBUG 18288 --- [nio-9999-exec-5] .w.a.p.RequestHeaderAuthenticationFilter : preAuthenticatedPrincipal = hello, trying to authenticate
2023-10-01T04:24:35.724+08:00 TRACE 18288 --- [nio-9999-exec-5] o.s.s.authentication.ProviderManager     : Authenticating request with MyPreAuthenticationProvider (1/1)
2023-10-01T04:24:35.733+08:00 DEBUG 18288 --- [nio-9999-exec-5] .w.a.p.RequestHeaderAuthenticationFilter : Authentication success: PreAuthenticatedAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=bwgjoseph, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[RW]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=88E2161FD4E90002135A8B0F1C2E5897], Granted Authorities=[RW]]
2023-10-01T04:24:35.735+08:00 DEBUG 18288 --- [nio-9999-exec-5] w.c.HttpSessionSecurityContextRepository : Stored SecurityContextImpl [Authentication=PreAuthenticatedAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=bwgjoseph, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[RW]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=88E2161FD4E90002135A8B0F1C2E5897], Granted Authorities=[RW]]] to HttpSession [org.apache.catalina.session.StandardSessionFacade@78062cf4]
2023-10-01T04:24:35.752+08:00 TRACE 18288 --- [nio-9999-exec-5] o.s.security.web.FilterChainProxy        : Invoking RequestCacheAwareFilter (8/12)
2023-10-01T04:24:35.767+08:00 TRACE 18288 --- [nio-9999-exec-5] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderAwareRequestFilter (9/12)
2023-10-01T04:24:35.781+08:00 TRACE 18288 --- [nio-9999-exec-5] o.s.security.web.FilterChainProxy        : Invoking AnonymousAuthenticationFilter (10/12)
2023-10-01T04:24:35.782+08:00 TRACE 18288 --- [nio-9999-exec-5] o.s.security.web.FilterChainProxy        : Invoking SessionManagementFilter (11/12)
// omitted

Again, this could be my limited understading on spring-security. So do point out if I am missing something obvious. Am also happy to provide more information / clarification if necessary.

Thank you for looking into this!


update: wanted to add on this

Based on the session-management-docs, it states that using stateless will configure to use NullSecurityContextRepository but given what I've wrote above, and my observation, I had to manually define securityContext in my custom dsl.

public class DummyDsl extends AbstractHttpConfigurer<DummyDsl, HttpSecurity> {
    @Override
    public void init(HttpSecurity http) throws Exception {
        http
            .securityContext(context -> context.securityContextRepository(new NullSecurityContextRepository));
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
    }

    // omitted
}

If I define .securityContext, then there is no need for me to define .sessionManagement in my apiFilterChain

@sjohnr
Copy link
Member

sjohnr commented Oct 2, 2023

@bwgjoseph thanks for following up with the sample.

I have reviewed your sample, and I do see that the behavior is slightly different when applying the DummyDsl via spring.factories vs applying it directly in the apiFilterChain(). So I understand why this is confusing, and in fact I haven't dug deeply yet to be able to accurately explain the difference in behavior so I agree it's odd. I think that it has to do with the order in which things are configured. It most likely relates to framework internals around the use of http.setSharedObject(...) in SessionManagementConfigurer in the DSL. It could be that you've found a bug, but I don't yet see any bug here.

The reason it's difficult to spot any obvious bugs in that your configuration and expectations around session management seem incorrect, and don't take into account what I discussed in the linked comment:

First, each authentication filter is now configured with a SecurityContextRepository and is responsible for persisting the SecurityContext itself, making the SessionManagementFilter unnecessary.

Second, the behavior in 5.8 was to register SessionManagementFilter by default, with SessionCreationPolicy.STATELESS configuring things to avoid creating a session. In 6.x, a SessionManagementFilter is no longer registered by default. However, choosing SessionCreationPolicy.STATELESS does register one again.

In the case of RequestHeaderAuthenticationFilter, you need to call setSecurityContextRepository() yourself, since you are not using the DSL to configure it. (Indeed, it is not configurable via the DSL.) In addition, I would expect you to stop calling sessionManagement() in 6.x, and instead rely on the SecurityContextRepository you configured, which means your custom DSL is not necessary (or at least should be changed, but since you haven't explained your use case I can't predict how it should change).

My best guess as to what you should be doing instead is:

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity(debug = true)
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
        http
            // Authorization is added to make it clear what's intended by this filter chain
            .authorizeHttpRequests((authorize) -> authorize
                .anyRequest().authenticated()
            );

        // Authentication is configured via DummyDsl which is registered via spring.factories

        return http.build();
    }

}

public class DummyDsl extends AbstractHttpConfigurer<DummyDsl, HttpSecurity> {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter =
            new RequestHeaderAuthenticationFilter();
        requestHeaderAuthenticationFilter.setPrincipalRequestHeader("X-User");
        requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(true);

        AuthenticationManager authenticationManager =
            new ProviderManager(new MyPreAuthenticationProvider());
        requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager);

        RequestAttributeSecurityContextRepository securityContextRepository =
            new RequestAttributeSecurityContextRepository();
        requestHeaderAuthenticationFilter.setSecurityContextRepository(securityContextRepository);

        http.addFilter(requestHeaderAuthenticationFilter);
    }

    // ...
}

This configuration is simpler, avoids use of sessionManagement() in 6.x, does not create any sessions nor load anything from a session, and still makes use of the custom DSL. For now, I'm going to leave this issue closed, and I hope that the above explanation helps you. If you have further questions, please open a stackoverflow question and share the link.

One final note: As I mentioned in the other issue, I do believe updates to the documentation are needed here, so that ticket will remain open.

@bwgjoseph
Copy link
Author

Thanks for the explanation, I think I'm having a better idea w.r.t SecurityContextRepository now.

I went into AbstractPreAuthenticatedProcessingFilter class, and see that the default is HttpSessionSecurityContextRepository which according to the docs, it means it stores the security context in the HttpSession between requests. And I think I do get why I should switch to RequestAttributeSecurityContextRepository because I don't want to save the session between request but I don't quite get why the default is set to HttpSessionSecurityContextRepository? Shouldn't PreAuthenticated requests always be "re-evaluated"? And since that HttpSessionSecurityContextRepository is the default, when I make any API request, it will always throw this exception

023-10-04T00:50:00.282+08:00 TRACE 7204 --- [nio-9999-exec-1] o.s.s.authentication.ProviderManager     : Authenticating request with MyPreAuthenticationProvider (1/1)
2023-10-04T00:50:00.285+08:00 DEBUG 7204 --- [nio-9999-exec-1] .w.a.p.RequestHeaderAuthenticationFilter : Authentication success: PreAuthenticatedAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=bwgjoseph, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[RW]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[RW]]
2023-10-04T00:50:00.438+08:00  WARN 7204 --- [nio-9999-exec-1] o.a.c.util.SessionIdGeneratorBase        : Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [140] milliseconds.
2023-10-04T00:50:00.468+08:00  INFO 7204 --- [nio-9999-exec-1] Spring Security Debugger                 : 

************************************************************

New HTTP session created: EC039CCFFC3DBF8EBADEE88F08B08ECA

Call stack:

        at org.springframework.security.web.debug.Logger.info(Logger.java:46)
        at org.springframework.security.web.debug.DebugFilter$DebugRequestWrapper.getSession(DebugFilter.java:171)
        at org.springframework.security.web.debug.DebugFilter$DebugRequestWrapper.getSession(DebugFilter.java:181)
        at jakarta.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:221)
        at jakarta.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:221)
        at org.springframework.security.web.context.HttpSessionSecurityContextRepository.saveContextInHttpSession(HttpSessionSecurityContextRepository.java:169)
        at org.springframework.security.web.context.HttpSessionSecurityContextRepository.saveContext(HttpSessionSecurityContextRepository.java:152)
        at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.successfulAuthentication(AbstractPreAuthenticatedProcessingFilter.java:221)
        at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.doAuthenticate(AbstractPreAuthenticatedProcessingFilter.java:201)
        at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.doFilter(AbstractPreAuthenticatedProcessingFilter.java:142)
// omitted
2023-10-04T00:50:00.478+08:00 DEBUG 7204 --- [nio-9999-exec-1] w.c.HttpSessionSecurityContextRepository : Stored SecurityContextImpl [Authentication=PreAuthenticatedAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=bwgjoseph, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[RW]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[RW]]] to HttpSession [org.apache.catalina.session.StandardSessionFacade@2368adaa] 
2023-10-04T00:50:00.481+08:00 TRACE 7204 --- [nio-9999-exec-1] o.s.security.web.FilterChainProxy        : Invoking RequestCacheAwareFilter (8/11)

Is this expected?


This configuration is simpler, avoids use of sessionManagement() in 6.x, does not create any sessions nor load anything from a session, and still makes use of the custom DSL

Thanks for showing this, and I do agree that this configuration is better. However, for my case, I'm not sure if the correct approach is to make custom dsl configurable from the consumer (like provide the class to custom dsl)

For example, my RequestHeaderAuthenticationFilter is actually taking in AuthenticationManager and AuthenticationDetailsSource bean which I do have a default beans declared, but it can be overwritten by consumer of the library.

I'm trying to write a common library that can be re-use across projects

// example
@Bean
@ConditionalOnMissingBean
public AuthenticationManager authenticationManager(AuthenticationProvider authenticationProvider) {
        return new ProviderManager(authenticationProvider);
}

@Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
    RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();
    requestHeaderAuthenticationFilter.setPrincipalRequestHeader("X-User");
    requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(true);
    requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager);
    requestHeaderAuthenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource);

    RequestAttributeSecurityContextRepository securityContextRepository =
        new RequestAttributeSecurityContextRepository();
    requestHeaderAuthenticationFilter.setSecurityContextRepository(securityContextRepository);

    return requestHeaderAuthenticationFilter;
}

In this case, if I were to define RequestHeaderAuthenticationFilter within the custom dsl, then that would mean that it can only be configured something like this?

public class DummyDsl extends AbstractHttpConfigurer<DummyDsl, HttpSecurity> {

    @Override
    public void init(HttpSecurity http) throws Exception {
        http.formLogin(AbstractHttpConfigurer::disable);

        // so is it ok to use this way to grab the correct bean, to set to authenticationManager?
        ApplicationContext context = http.getSharedObject(ApplicationContext.class);

        AuthenticationManager authenticationManager = context.getBean(AuthenticationManager.class);
        // actually i'm not sure how to grab generic type of bean from context correctly
        AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = context.getBean(AuthenticationDetailsSource.class);

        RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();
        requestHeaderAuthenticationFilter.setPrincipalRequestHeader("X-User");
        requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(true);
        requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager);
        requestHeaderAuthenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource);

        RequestAttributeSecurityContextRepository securityContextRepository =
            new RequestAttributeSecurityContextRepository();
        requestHeaderAuthenticationFilter.setSecurityContextRepository(securityContextRepository);
    }
    // omitted
}

public class WebSecurityConfig {
    @Bean
    public SecurityFilterChain apiFilterChain(HttpSecurity http, RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter) throws Exception {
        return http
                // Authorization is added to make it clear what's intended by this filter chain
                .authorizeHttpRequests((authorize) -> authorize
                .anyRequest().authenticated()
            )
            .build();
    }
    // omitted
}

This way, I'm still allowing users to create their own AuthenticationManager and AuthenticationDetailsSource bean, which will be picked up in custom dsl through http.getSharedObject(ApplicationContext.class) which then get passed to RequestHeaderAuthenticationFilter. This makes it such that there's no need to manually add .addFilter(requestHeaderAuthenticationFilter) in each of the SecurityFilterChain, right?

It could be that you've found a bug, but I don't yet see any bug here.

You mentioned this, but from my perspective, if the behavior differs, wouldn't it be considered as bug, or rather requires deeper investigation (as you have pointed out it could be http.setSharedObject(...))?

Hope I'm not making it more confusing, and thanks for your patience!

@sjohnr
Copy link
Member

sjohnr commented Oct 3, 2023

It could be that you've found a bug, but I don't yet see any bug here.

You mentioned this, but from my perspective, if the behavior differs, wouldn't it be considered as bug, or rather requires deeper investigation (as you have pointed out it could be http.setSharedObject(...))?

If you believe you have found a bug (I currently do not), please provide a minimal sample that reproduces it. In this case, I would suggest removing anything with custom authentication, and then point to what is broken when you use the filter chain bean vs the custom DSL.

Regarding all other questions, please go to stackoverflow for this. I am happy to answer questions, but this is not the place for that as I have mentioned already.

@bwgjoseph
Copy link
Author

I just ran another round of testing, and it does seem that it is a lack of understanding on my end, rather than a bug as suspected initially.

Just to close it off, and hope that I got it right. It seems like the misunderstanding here (or cause of confusion) is the usage of RequestHeaderAuthenticationFilter which register HttpSessionSecurityContextRepository by default that results in storing HttpSession between requests as opposed to using RequestAttributeSecurityContextRepository that does not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: stackoverflow A question that's better suited to stackoverflow.com
Projects
None yet
Development

No branches or pull requests

2 participants