В моем проекте есть серия интеграционных тестов, в которых используются 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
?
Я думаю, вам также может понадобиться передать -Djavax.net.ssl.trustStore= -Djavax.net.ssl.trustStorePassword= параметры при выполнении тестов. Для запуска одиночных тестовых аргументов в конфигурации и в maven вы также можете передать эти параметры.
Ниже две ссылки могут помочь
Указание информации о доверенном хранилище в spring boot application.properties
Спасибо, первая ссылка, которую вы разместили, была очень полезной. Это мой рабочий код для 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 и использующих самозаверяющие сертификаты для разработки (правильные сертификаты, рекомендуемые для производства - см. 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
thencurl
will returncurl: (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 aTestRestTemplate
instance.