WEB/Security

Lesson 33,34 - Integration testing for Spring Security implementations - Part 1,2

Tony Lim 2022. 5. 22. 15:43
728x90

test doesn't go whole cycle of spring security instad mock create Security context also assuming authentication had worked already

authentication , authorization test are seperated

@SpringBootTest
@AutoConfigureMockMvc
class Example1Tests {

  @Autowired
  private MockMvc mockMvc;

  @Test
  @DisplayName("When calling the /demo endpoint without authentication we expect to get a 401 Unauthorized.")
  void testUnauthenticatedDemoEndpoint() throws Exception {
    mockMvc.perform(get("/demo"))
        .andExpect(status().isUnauthorized());
  }

  @Test
  @WithMockUser
  void testAuthenticatedWithoutProperAuthDemoEndpoint() throws Exception {
    mockMvc.perform(get("/demo"))
        .andExpect(status().isForbidden());
  }
}

mockmvc let you test what client will get if he calls my rest api 

here we are simply testing Authorization not Authentication, we assume we are already authenticated somehow.

 

name of method need to be short , when you wan to tell story abouth this method use @DisplayName

never user network when writing test like database

 

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

  @Bean
  public UserDetailsService userDetailsService() {
    var uds = new InMemoryUserDetailsManager();

    var u1 = User.withUsername("john")
        .password("12345")
        .authorities("read")
        .build();

    var u2 = User.withUsername("bill")
        .password("12345")
        .authorities("write")
        .build();

    uds.createUser(u1);
    uds.createUser(u2);

    return uds;
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.httpBasic();

    http.authorizeRequests()
        .anyRequest().hasAuthority("read");
  }
@Test
@WithMockUser(username = "mary", authorities = "read")
void testAuthenticatedWithProperAuthDemoEndpoint() throws Exception {
  mockMvc.perform(get("/demo"))
      .andExpect(status().isOk());
}

now we are considering Authorization (read)

notice there is no user mary but still works because we are skipping authentication

 

@SpringBootTest
@AutoConfigureMockMvc
class Example2Tests {

  @Autowired
  private MockMvc mockMvc;

  @Test
  @DisplayName("When calling the /demo endpoint without authentication we expect to get a 401 Unauthorized.")
  void testUnauthenticatedDemoEndpoint() throws Exception {
    mockMvc.perform(get("/demo"))
        .andExpect(status().isUnauthorized());
  }

  @Test
  @WithUserDetails("bill")
  void testAuthenticatedWithoutProperAuthDemoEndpoint() throws Exception {
    mockMvc.perform(get("/demo"))
        .andExpect(status().isForbidden());
  }

  @Test
  @WithUserDetails("john")
  void testAuthenticatedWithProperAuthDemoEndpoint() throws Exception {
    mockMvc.perform(get("/demo"))
        .andExpect(status().isOk());
  }
}

we can write test with actual user instead of mock user

 


 

public class WithCustomSecurityContextFactory
    implements WithSecurityContextFactory<WithCustomUser> {

  @Override
  public SecurityContext createSecurityContext(WithCustomUser withCustomUser) {
    SecurityContext context = SecurityContextHolder.createEmptyContext();

    Authentication a =
        new UsernamePasswordAuthenticationToken("bill", null,
            List.of(() -> withCustomUser.authority()));

    context.setAuthentication(a);

    return context;
  }
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@WithSecurityContext(factory = WithCustomSecurityContextFactory.class)
public @interface WithCustomUser {

  String authority();
}

create custom Security Context

WithCustomUser -> name of annotation

CustomSecurityContextFactory gets authority from given annotation.authoirty()

@Test
@WithCustomUser(authority = "read")
void testAuthenticatedWithAProperAuthDemoEndpoint() throws Exception {
  mockMvc.perform(get("/demo"))
      .andExpect(status().isOk());
}

spring sees @WithCustomUser -> see @WithSecurityContext and know where to get security context from

 

@Test
void testAuthenticatedWithProperAuthDemoEndpoint() throws Exception {
  mockMvc.perform(
      get("/demo").with(httpBasic("john", "12345"))
  ).andExpect(status().isOk());
}

test for authentication -> here we used basic auth(httpBasic)

but we can also use jwt() for jwt validation or opaqueToken()

 

 

 

728x90