Как подделать аутентификацию JWT в модульном тесте Spring Boot? - PullRequest
3 голосов
/ 29 апреля 2020

Я добавил JWT Аутентификацию с использованием Auth0 к моему REST API Spring Boot, следуя этому примеру .

Теперь, как и ожидалось, мои ранее работающие Controller модульные тесты дают код ответа 401 Unauthorized, а не 200 OK, поскольку я не передаю JWT в тестах.

Как я могу издеваться над частью JWT/Authentication моих тестов REST Controller?

Класс модульного теста:

    @AutoConfigureMockMvc
    public class UserRoundsControllerTest extends AbstractUnitTests {

        private static String STUB_USER_ID = "user3";
        private static String STUB_ROUND_ID = "7e3b270222252b2dadd547fb";

        @Autowired
        private MockMvc mockMvc;

        private Round round;

        private ObjectId objectId;

        @BeforeEach
        public void setUp() {
            initMocks(this);
            round = Mocks.roundOne();
            objectId = Mocks.objectId();
        }

        @Test
        public void shouldGetAllRoundsByUserId() throws Exception {

            // setup
            given(userRoundService.getAllRoundsByUserId(STUB_USER_ID)).willReturn(Collections.singletonList(round));

            // mock the rounds/userId request
            RequestBuilder requestBuilder = Requests.getAllRoundsByUserId(STUB_USER_ID);

            // perform the requests
            MockHttpServletResponse response = mockMvc.perform(requestBuilder)
                .andReturn()
                .getResponse();

            // asserts
            assertNotNull(response);
            assertEquals(HttpStatus.OK.value(), response.getStatus());
        }

        //other tests
}

Запросы класс (используется выше):

public class Requests {

    private Requests() {
    }

    public static RequestBuilder getAllRoundsByUserId(String userId) {

        return MockMvcRequestBuilders
            .get("/users/" + userId + "/rounds/")
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON);
    }

}

Spring Securty Config:

/**
 * Configures our application with Spring Security to restrict access to our API endpoints.
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${auth0.audience}")
    private String audience;

    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private String issuer;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        /*
        This is where we configure the security required for our endpoints and setup our app to serve as
        an OAuth2 Resource Server, using JWT validation.
        */

        http.cors().and().csrf().disable().sessionManagement().
            sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
            .mvcMatchers(HttpMethod.GET,"/users/**").authenticated()
            .mvcMatchers(HttpMethod.POST,"/users/**").authenticated()
            .mvcMatchers(HttpMethod.DELETE,"/users/**").authenticated()
            .mvcMatchers(HttpMethod.PUT,"/users/**").authenticated()
            .and()
            .oauth2ResourceServer().jwt();
    }

    @Bean
    JwtDecoder jwtDecoder() {
        /*
        By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
        indeed intended for our app. Adding our own validator is easy to do:
        */

        NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
            JwtDecoders.fromOidcIssuerLocation(issuer);

        OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

        jwtDecoder.setJwtValidator(withAudience);

        return jwtDecoder;
    }


    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

Абстрактный юнит-тестовый класс:

@ExtendWith(SpringExtension.class)
@SpringBootTest(
    classes = PokerStatApplication.class,
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
public abstract class AbstractUnitTests {

   // mock objects etc


}

Ответы [ 3 ]

1 голос
/ 29 апреля 2020

Компонент SecurityConfig может быть загружен условно как,

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  @Profile("!test")
  public WebSecurityConfigurerAdapter securityEnabled() {

    return new WebSecurityConfigurerAdapter() {

      @Override
      protected void configure(HttpSecurity http) throws Exception {
        // your code goes here
      }

    };
  }

  @Bean
  @Profile("test")
  public WebSecurityConfigurerAdapter securityDisabled() {

    return new WebSecurityConfigurerAdapter() {

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

}

Таким образом, этот компонент не будет инициализирован в случае тестового профиля. Это означает, что теперь защита отключена и все конечные точки доступны без заголовка авторизации.

Теперь «тестовый» профиль должен быть активным в случае запуска тестов, это можно сделать как,

@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@WebMvcTest(UserRoundsController.class)
public class UserRoundsControllerTest extends AbstractUnitTests {

// your code goes here

}

Теперь этот тест будет запущен с профилем "test". Кроме того, если вы хотите иметь какие-либо свойства, связанные с этим тестом, их можно поместить в src / test / resources / application-test.properties.

Надеюсь, это поможет! пожалуйста, дайте мне знать иначе.

Обновление: Основная идея c - отключить защиту для тестового профиля. В предыдущем коде, даже после указания профиля c bean, защита по умолчанию включалась.

0 голосов
/ 10 мая 2020

создать application.properties в тесте / ресурсах (оно переопределяет основной, но только для этапа тестирования)

защита выключения по заданию:

security.ignored=/**
security.basic.enable= false
spring.autoconfigure.exclude= org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
0 голосов
/ 09 мая 2020

Если вы хотите протестировать часть своего приложения, вам следует загружать только те конфигурации, которые вам нужны. в вашем случае вы должны загружать свои контроллеры только с помощью @WebMvcTest(controllers = ...,...,) вместо @SpringBootTest(...), которые загружают весь контекст, включая настройки безопасности. поэтому просто замените его, и оно должно работать как положено

имейте в виду, что вы должны создать интеграционные тесты, чтобы убедиться, что ваше приложение защищено должным образом, на этот раз вам нужно использовать @SpringBootTest(...) для загрузки всего контекста, и вы нужно будет создать тестового пользователя в auth0, чтобы использовать его для тестирования вашей реализации

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...