spring-security icon indicating copy to clipboard operation
spring-security copied to clipboard

SecurityContextHolder is not populated in `@BeforeAll/PostConstruct` within `@WithUserDetails`

Open bwgjoseph opened this issue 1 year ago • 0 comments

Describe the bug

I'm not sure if this is the intended behavior where SecurityContextHolder is not populated or accessible within @BeforeAll/PostConstruct. I searched the repository and found https://github.com/spring-projects/spring-security/issues/6591 is quite close to what I experience/encounter.

This is reproduced using Spring Boot 3.3.4 but I encountered it in my project which is using Spring Boot 3.2.5, with Java 21 and JUnit 5

To Reproduce

This is the code snippet to reproduce

@ExtendWith(SpringExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DemoBugTests {
    SoftAssertions softAssertions = new SoftAssertions();

    @PostConstruct
    void postConstruct() {
        // authentication object is null
        // SecurityContextImpl [Null authentication]
        softAssertions.assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull(); // this fail
    }

    @BeforeAll
    void beforeAll() {
        // authentication object is null
        // SecurityContextImpl [Null authentication]
        softAssertions.assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull(); // this fail
    }

    @BeforeEach
    void beforeEach() {
        // SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=bwgjoseph, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, CredentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[admin]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[admin]]]
        softAssertions.assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull(); // this pass
    }

    @Test
    @WithUserDetails
    void validateAuthentication() {
        // SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=bwgjoseph, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, CredentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[admin]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[admin]]]
        softAssertions.assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull(); // this pass
        softAssertions.assertAll();
    }

    @TestConfiguration
    static class SecurityConfiguration {
        @Bean
        UserDetailsService userDetailsService() {
            return new TestUserDetails();
        }
    }

    static class TestUserDetails implements UserDetailsService {

        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            return new User("bwgjoseph", "pass", List.of(new SimpleGrantedAuthority("admin")));
        }
    }
}

Expected behavior

I would expect that the authentication object is available within @BeforeAll and PostConstruct method.

I'm not using @PostConstruct actually, but added in this test to see the behavior

Extra Note

I discovered this as my current test is failing where there's a method call to insert certain data in @BeforeAll which uses SpEL within the repository method

@Query(...#{principal}...)
Data findById(...)

So the stack trace looks like the following...

... SpelEvaluationException: EL1022E: A problem occurred whilst attempting to access the property 'principal': The function 'principal' mapped to an object of type 'class org.springframework.security.access.expression.SecurityExpressionRoot' cannot be invoked
// omitted
... SpelEvaluationException: EL1022E: The function 'principal' mapped to an object of type 'class org.springframework.security.access.expression.SecurityExpressionRoot' cannot be invoked

So after some tracing, I found out that it was because the authentication object was null when called in @BeforeAll, and thus, causing the test to fail.

I'm also using custom @WithSecurityContext to provide my own @WithMockXXXUser annotation if that matters.

Let me know if more information is required.

Thanks!

bwgjoseph avatar Oct 12 '24 06:10 bwgjoseph