Как настроить TestRestTemplate для использования хранилища ключей?

В моем проекте есть серия интеграционных тестов, в которых используются TestRestTemplate и MockMvc. Они проходили успешно.

Теперь я добавил зависимости Spring Boot Starter Security и Spring Security OAuth2 Autoconfigure в свой проект. Я добавил пользовательский класс, который расширяет WebSecurityConfigurerAdapter, чтобы разрешить открытый доступ (на данный момент) к моему приложению. Вот класс

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

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

    @Override
    public void configure(WebSecurity webSecurity) {
        webSecurity
            .ignoring()
            .antMatchers(HttpMethod.OPTIONS, "/**");
    }
}

Приложение также должно действовать как OAuth2 Resource Server, поэтому я также пометил свой основной класс @EnableResourceServer. Я указываю путь к доверенному хранилищу ключей в качестве параметров запуска при запуске приложения. -Djavax.net.ssl.trustStore=<where the cert is stored locally> -Djavax.net.ssl.trustStorePassword=<the password>

Приложение работает нормально, но теперь все интеграционные тесты не проходят. Вот пример ошибки, общей для всех тестов, использующих TestRestTemplate

Could not fetch user details: class org.springframework.web.client.ResourceAccessException, I/O error on GET request for <the path to my userinfo URL>: 
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: 
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Кажется, что TestRestTemplate, который я использую для своих тестов, должен быть проинструктирован использовать то же хранилище ключей, что и приложение. Можно ли сделать это? Как это сработает для MockMvc?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
1 612
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Я думаю, вам также может понадобиться передать -Djavax.net.ssl.trustStore= -Djavax.net.ssl.trustStorePassword= параметры при выполнении тестов. Для запуска одиночных тестовых аргументов в конфигурации и в maven вы также можете передать эти параметры.

Ниже две ссылки могут помочь

Указание информации о доверенном хранилище в spring boot application.properties

http://codeboarding.com/tag/testresttemplate/

Спасибо, первая ссылка, которую вы разместили, была очень полезной. Это мой рабочий код для RestTemplate, который принимает любой сертификат, если кто-то еще найдет его полезным. Это все еще зависит от предоставления действительных токенов, но это уже другая история.

private RestTemplate buildRestTemplate() throws Exception {
    SSLContext sslContext = new SSLContextBuilder()
        .loadTrustMaterial(
            new TrustSelfSignedStrategy()
        ).build();
    SSLConnectionSocketFactory socketFactory =
        new SSLConnectionSocketFactory(sslContext);
    HttpClient httpClient = HttpClients.custom()
        .setSSLSocketFactory(socketFactory).build();
    HttpComponentsClientHttpRequestFactory factory =
        new HttpComponentsClientHttpRequestFactory(httpClient);
    return new RestTemplate(factory);
}

Решение для Spring Boot 2

Следующий ответ предназначен для людей, разрабатывающих Spring Boot 2 и использующих самозаверяющие сертификаты для разработки (правильные сертификаты, рекомендуемые для производства - см. https://letsencrypt.org/).

Вы можете создать файл хранилища ключей, содержащий самоподписанные сертификаты, с помощью команды keytool: -

keytool -genkey -storetype PKCS12 \
    -alias selfsigned_localhost_sslserver \
    -keyalg RSA -keysize 2048 -validity 3650 \
    -dname "CN=localhost, OU=Engineering, O=Acme Corp, L=New York, S=New York, C=US" \
    -noprompt -keypass changeit -storepass changeit \
    -keystore keystore-self-signed.p12

Файл keystore-self-signed.p12 будет содержать самоподписанный сертификат, и этот файл можно переместить в папку src/main/resources (или src/test/resources, если хотите).

Добавьте следующее в конфигурацию application.yaml Spring, чтобы использовать SSL и указать на хранилище ключей: -

server:
  port: 443
  ssl:
    enabled: true
    key-store: classpath:keystore-self-signed.p12
    key-store-type: PKCS12
    protocol: TLS
    enabled-protocols: TLSv1.2   # Best practice - see https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices
    key-password: changeit
    key-store-password: changeit

Давайте создадим очень простую конечную точку контроллера Spring Boot для тестирования:

@RestController
public class PingController {

    @GetMapping("/ping")
    public ResponseEntity<String> ping() {
        return new ResponseEntity<>("pong", HttpStatus.OK);
    }

}

Теперь мы можем попасть в эту конечную точку с помощью команды curl (или Postman), т.е.

$ curl https://localhost/ping --insecure --silent
pong

Note: if we don't include --insecure then curl will return curl: (60) SSL certificate problem: self signed certificate.

Чтобы протестировать правильный тест интеграции Spring Boot с его конечной точкой, используя TestRestTemplate, мы можем сделать следующее: -

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class PingControllerTest {

    @Value("${server.ssl.key-store}")
    private Resource keyStore;   // inject keystore specified in config

    @Value("${server.ssl.key-store-password}")
    private String keyStorePassword;  // inject password from config

    @LocalServerPort
    protected int port;   // server port picked randomly at runtime

    private TestRestTemplate restTemplate;

    @Before
    public void setup() throws Exception {
        SSLContext sslContext = new SSLContextBuilder()
            .loadTrustMaterial(
                keyStore.getURL(),
                keyStorePassword.toCharArray()
            ).build();
        SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
        HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
            httpClient);
        RestTemplateBuilder rtb = new RestTemplateBuilder()
            .requestFactory(() -> factory)
            .rootUri("https://localhost:" + port);
        this.restTemplate = new TestRestTemplate(rtb, null, null, HttpClientOption.SSL);
    }

    @Test
    public void shouldPing() {
        ResponseEntity<String> result = restTemplate.getForEntity("/ping", String.class);
        assertEquals(HttpStatus.OK, result.getStatusCode());
        assertEquals("pong", result.getBody());
    }


}

Как видите, метод setup создает экземпляр объекта SSLContext, который загружает (и «доверяет») сертификаты самоподписания в файле keystore-self-signed.p12 (внедрены через объект Spring Resource).

Класс SSLContext внедряется в объект SSLConnectionSocketFactory, который, в свою очередь, внедряется в объект HttpClient, который затем внедряется в объект HttpComponentsClientHttpRequestFactory.

Этот фабричный объект, наконец, внедряется в экземпляр TestRestTemplate для использования в интеграционном тесте shouldPing.

ПРИМЕЧАНИЕ — я изначально потерял время со следующим кодом:

...
this.restTemplate = new TestRestTemplate(rgb);

...но это вернулось...

org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://localhost:56976/ping": 
    sun.security.validator.ValidatorException: PKIX path building failed: 
    sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is 
    javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: 
    sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

После отладки через TestRestTemplate я понял, что вы должны использовать конструктор с 4 параметрами TestRestTemplate с HttpClientOption.SSL т.е.

this.restTemplate = new TestRestTemplate(rtb, null, null, HttpClientOption.SSL);

Однако, если вы используете обычный RestTemplate (например, вне тестов Spring), тогда следующие работы: -

...
RestTemplate restTemplate = new RestTemplate(rgb);

NOTE, to improve - create a @Bean method which returns a TestRestTemplate instance.

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

Похожие вопросы

Несколько поставщиков аутентификации для oauth2
Логика выхода не работает с базовой аутентификацией
Как добавить один и тот же параметр для нескольких идентичных страниц входа, если аутентификация не удалась?
Как сделать для авторизации конечных точек для анонимного пользователя с помощью весенней безопасности
Почему я не могу отключить автоматически сгенерированный пароль и пользователя весенней загрузки?
Сохраняйте имя пользователя в базе данных при выполнении вызовов API для отдыха или мыла из приложения A в приложение B в Java Spring 4
Как выйти из системы с запросом GET в SpringBoot WebFlux
LDAP: получение пользовательских значений во время события аутентификации
Инициализировать репозитории, защищенные аутентификацией Spring
Нужно ли помещать исходные учетные данные в UsernamePasswordAuthenticationToken после успешной аутентификации в AuthenticationProvider?