Как проверить код состояния http 401 (без аутентификации) с помощью MockMVC и Spring Boot OAuth2 Resource Server?

В настоящее время я разрабатываю приложение Spring Boot 3 , которое предоставляет REST API. Чтобы использовать этот API, пользователи должны пройти аутентификацию с помощью рабочего процесса OAuth2 нашего keycloak поставщика удостоверений. Поэтому я использовал org.springframework.boot:spring-boot-starter-oauth2-resource-server. Когда я запускаю приложение, аутентификация и авторизация работают должным образом.

К сожалению, я не могу написать WebMvcTest для варианта использования, когда пользователь не предоставляет JWT для аутентификации . В этом случае я ожидаю ответа HTTP с кодом состояния 401 (без аутентификации), но получаю код состояния 403 (запрещено). Возможно ли это событие, потому что MockMvc имитирует части обработки ответа?

Я успешно написал тестовые примеры для следующих вариантов использования.

  • Пользователь предоставляет JWT с ожидаемым требованием => Я ожидаю код состояния 200 ✔
  • Пользователь предоставляет JWT без ожидаемого утверждения => Я ожидаю код состояния 403 ✔

Я пытался следовать всему из документации Spring Security: https://docs.spring.io/spring-security/reference/servlet/test/index.html .

Вот мой код.

      @WebMvcTest(CustomerController.class)
@ImportAutoConfiguration(classes = {RequestInformationExtractor.class})
@ContextConfiguration(classes = SecurityConfiguration.class)
@Import({TestConfiguration.class, CustomerController.class})
public class PartnerControllerTest {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @BeforeEach
    public void setup() {
        mockMvc = MockMvcBuilders
            .webAppContextSetup(context)
            .apply(springSecurity())
            .build();
    }
    
    // runs successfully
    @Test
    void shouldReturnListOfCustomers() throws Exception {
        mockMvc.perform(
                    post("/search")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{" +
                                "\"searchKeyword\": \"Mustermann\"" +
                                "}")
                        .with(jwt()
                                .authorities(
                                        new SimpleGrantedAuthority("basic")
                                )))
        .andExpect(status().isOk());
    }

    // fails: expect 401 but got 403
    @Test
    void shouldReturn401WithoutJwt() throws Exception {
        mockMvc.perform(
                post("/search")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{" +
                                "\"searchKeyword\": \"Mustermann\"" +
                                "}"))
        .andExpect(status().isUnauthorized());
    }

    // runs successfully
    @Test
    void shouldReturn403() throws Exception {

        mockMvc.perform(
                post("/search")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{" +
                                "\"searchKeyword\": \"Mustermann\"" +
                                "}")
                        .with(jwt()))
          .andExpect(status().isForbidden());
    }
}
      @org.springframework.boot.test.context.TestConfiguration
public class TestConfiguration {

    @Bean
    public JwtDecoder jwtDecoder() {
        SecretKey secretKey = new SecretKeySpec("dasdasdasdfgsg9423942342394239492349fsd9fsd9fsdfjkldasd".getBytes(), JWSAlgorithm.HS256.getName());
        NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(secretKey).build();
        return jwtDecoder;
    }
}
      @Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(STATELESS))
                .authorizeHttpRequests((authz) -> authz
                        .requestMatchers("/actuator/**").permitAll()
                        .anyRequest().hasAuthority("Basic")
                )
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
        return http.build();
    }

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthoritiesClaimName("groups");
        grantedAuthoritiesConverter.setAuthorityPrefix("");

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}

1 ответ

Вероятно, у вас есть 403, потому что исключение выдается до оценки контроля доступа (CORS или CSRF или что-то в этом роде).

Например, в вашей конфигурации безопасности вы отключаете сеансы (политика создания сеансов для безгражданства), но не защиту CSRF. Либо отключите CSRF в вашей конфигурации (вы можете, потому что атаки CSRF используют сеансы), либо используйте MockMvccsrf()постпроцессор в ваших тестах.

В моих примерах и туториалах есть много демонстраций серверов-ресурсов с конфигурацией безопасности и тестами (модульными и интеграционными) . В большинстве из них есть ссылки на мои тестовые аннотации и загрузчики (которые позволяют определить почти всю конфигурацию безопасности из свойств без конфигурации Java), но этот не использует ничего из моих расширений. Там вы должны найти полезные советы для вашей безопасности и тестов.

Другие вопросы по тегам