如何配置TestRestTemplate使用密钥库?

时间:2019-05-30 12:14:34

标签: spring-security integration-testing keystore spring-security-oauth2 oidc

我的项目有一系列使用TestRestTemplateMockMvc的集成测试。这些已经顺利通过。

我现在已经向项目添加了Spring Boot Starter SecuritySpring 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,它将如何工作?

3 个答案:

答案 0 :(得分:2)

我认为您可能还需要传递-Djavax.net.ssl.trustStore = -Djavax.net.ssl.trustStorePassword = 测试时输入参数。 为了在配置和maven中运行单个测试通过参数,您也可以传递这些参数。

下面两个链接可能有帮助

Specifying trust store information in spring boot application.properties

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

答案 1 :(得分:0)

谢谢,您发布的第一个链接非常有用。这是我接受任何证书的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);
}

答案 2 :(得分:0)

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文件中的自签名证书(通过春天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的4个参数构造函数与HttpClientOption.SSL一起使用,即

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

但是,如果您使用的是普通的RestTemplate(例如,在Spring测试之外),则 以下作品:-

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

注意,要改进-创建一个@Bean方法,该方法返回一个TestRestTemplate实例。