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 命令(或邮递员)访问此端点,即
$ curl https://localhost/ping --insecure --silent
pong
注意:如果我们不包含--insecure,那么curl 将返回curl: (60) SSL certificate problem: self signed certificate。
要使用TestRestTemplate 对其端点进行适当的 Spring Boot 集成测试,我们可以执行以下操作:-
@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调试后,我意识到你必须使用TestRestTemplate和HttpClientOption.SSL的4参数构造函数,即
this.restTemplate = new TestRestTemplate(rtb, null, null, HttpClientOption.SSL);
但是,如果您使用的是普通的 RestTemplate(例如在 Spring 测试之外),那么
以下作品:-
...
RestTemplate restTemplate = new RestTemplate(rgb);
注意,为了改进 - 创建一个返回 TestRestTemplate 实例的 @Bean 方法。