如何配置 TestRestTemplate 以使用密钥库?
How to configure TestRestTemplate to use a Keystore?
我的项目有一系列使用 TestRestTemplate
和 MockMvc
的集成测试。这些都顺利通过了。
我现在已将 Spring Boot Starter Security
和 Spring Security OAuth2 Autoconfigure
依赖项添加到我的项目中。我添加了一个扩展 WebSecurityConfigurerAdapter
的自定义 class 以允许(暂时)对我的应用程序进行开放访问。这是 class
@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
注释了我的主要 class。在 运行 应用程序时,我将可信密钥存储的路径作为 运行 参数提供。 -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 中也可以传递这些参数。
以下两个链接可能有帮助
Specifying trust store information in spring boot application.properties
谢谢,您发的第一个 link 非常有用。这是我接受任何证书的 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 启动 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 要测试的引导控制器端点:-
@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
.
要使用 TestRestTemplate
测试正确的 Spring 启动集成测试到他的端点,那么我们可以执行以下操作:-
@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
class 被注入到 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
调试后,我意识到必须将 TestRestTemplate
的 4 参数构造函数与 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.
我的项目有一系列使用 TestRestTemplate
和 MockMvc
的集成测试。这些都顺利通过了。
我现在已将 Spring Boot Starter Security
和 Spring Security OAuth2 Autoconfigure
依赖项添加到我的项目中。我添加了一个扩展 WebSecurityConfigurerAdapter
的自定义 class 以允许(暂时)对我的应用程序进行开放访问。这是 class
@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
注释了我的主要 class。在 运行 应用程序时,我将可信密钥存储的路径作为 运行 参数提供。 -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 中也可以传递这些参数。
以下两个链接可能有帮助
Specifying trust store information in spring boot application.properties
谢谢,您发的第一个 link 非常有用。这是我接受任何证书的 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 启动 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 要测试的引导控制器端点:-
@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
.
要使用 TestRestTemplate
测试正确的 Spring 启动集成测试到他的端点,那么我们可以执行以下操作:-
@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
class 被注入到 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
调试后,我意识到必须将 TestRestTemplate
的 4 参数构造函数与 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.