7

I have a spring boot (version 1.5.9.RELEASE) application which uses spring-session to store sessions on Redis. It also uses spring-security to authenticate users. When running the application, after a successful login, the security context contains the Authentication object. But when running unit tests I get this error message Authentication should not be null. Code to reproduce is the following:

@SpringBootApplication
public class DemoRedisDataSessionApplication {

    @Configuration
    @EnableWebSecurity
    @EnableRedisHttpSession(redisNamespace = "demo-redis-spring-session")
    public static class AppConfiguration extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication().withUser("user").password("0000").roles("USER");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin().and()
                    .authorizeRequests().anyRequest().fullyAuthenticated();
        }

    }

    @RestController
    public static class AppController {

        @GetMapping("/secured")
        public String secured() {
            return "secured";
        }

    }

    public static void main(String[] args) {
        SpringApplication.run(DemoRedisDataSessionApplication.class, args);
    }

}

Here is application.properties

spring.session.store-type=redis

Here is the failing test

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class DemoRedisDataSessionApplicationTests {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testUserShouldBeAuthenticated() throws Exception {
        mockMvc.perform(formLogin().user("user").password("0000"))
                .andExpect(status().is3xxRedirection())
                .andExpect(authenticated());
    }

}

Error message for the failed test:

java.lang.AssertionError: Authentication should not be null

    at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:35)
    at org.springframework.test.util.AssertionErrors.assertTrue(AssertionErrors.java:65)
    at org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers$AuthenticatedMatcher.match(SecurityMockMvcResultMatchers.java:98)

In particular it seems that the session is null in the class HttpSessionSecurityContextRepository line 110, but I don't understand why.

I expect the user to be authenticated and the SecurityContext populated after a successful login. Do you have any idea on how to solve this?

Fabio Maffioletti
  • 820
  • 1
  • 8
  • 17

1 Answers1

0

Updated:

Firstly, you need to instruct your authentication provider (in your case, it is the default DaoAuthenticationProvider) to return what kind of Authentication object. For instance, you can add httpBasic() into your configure(HttpSecurity http) method in your customized WebSecurityConfigurerAdapter. Essentially, httpBasic() will convert your username and password to a UsernamePasswordAuthenticationToken object such that your DaoAuthenticationProvider can use it to do authentication.

In addition, you need to permitAll for you login url.

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().and()
                .authorizeRequests()
                .antMatchers("/login/**").permitAll()
                .anyRequest().fullyAuthenticated()
                .and().httpBasic();
    }

With regards to the unit test, the issue was due to the fact that you didn't wire-in spring security into your mockMvc object. As you are actually spring-boot, I would give you a sample solution with spring-boot-test:

@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
public class DemoRedisDataSessionApplicationTests {

@Autowired
WebApplicationContext wac;

private MockMvc mockMvc;

@Before
public void setUp() {

    mockMvc = MockMvcBuilders.webAppContextSetup(wac)
            .apply(springSecurity())
            .build();
}

@Test
public void testUserShouldBeAuthenticated() throws Exception {
    mockMvc.perform(formLogin().user("user").password("0000"))
            .andExpect(status().is3xxRedirection())
            .andExpect(authenticated());
}
}

Key Note: springSecurity() in the code above is from import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity.

imarchuang
  • 467
  • 7
  • 17
  • Thanks, but unfortunately I get the same error even with your proposed solution... I think the problem is that the `spring-session` library does not integrate very well with unit tests. – Fabio Maffioletti Jan 11 '18 at 06:57
  • shouldn't be related to spring-session for this specific error... could your try to add `httpBasic()` in your AppConfiguration's `protected void configure(HttpSecurity http) throws Exception` method? – imarchuang Jan 11 '18 at 07:03
  • Fabio, I mean this: `@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin().and() .authorizeRequests().anyRequest().fullyAuthenticated().and().httpBasic(); }` – imarchuang Jan 11 '18 at 16:36
  • With `httpBasic()` it works fine, but unfortunately we cannot use it... we need to stick to `formLogin()` – Fabio Maffioletti Jan 12 '18 at 08:48
  • Fabio, these 2 concepts don't clash with each other. `formLogin()` is to provide a implicit login page for your application, whereas `httpBasic()` is to provide a way for your authentication provider to authenticate the user. Essentially, `httpBasic()` will convert your username and password to a `UsernamePasswordAuthenticationToken` object such that your `DaoAuthenticationProvider` can use it to do authentication. – imarchuang Jan 12 '18 at 14:40