spring-security - 如何配置 TestRestTemplate 以使用 keystore ?

标签 spring-security integration-testing keystore spring-security-oauth2 openid-connect

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

我现在已经添加了Spring Boot Starter SecuritySpring Security OAuth2 Autoconfigure对我的项目的依赖。我添加了一个扩展 WebSecurityConfigurerAdapter 的自定义类允许(暂时)对我的应用程序进行开放访问。这是类(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 注释了我的主类。运行应用程序时,我提供受信任 key 存储的路径作为运行参数。 -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我用于测试的需要被指示使用与应用程序相同的 keystore 。是否有可能做到这一点? MockMvc 会如何工作? ?

最佳答案

Spring Boot 2 的解决方案

以下答案针对的是针对 Spring Boot 2 进行开发并使用自签名证书进行开发的人员(推荐用于生产的适当证书 - 请参阅 https://letsencrypt.org/ )。

您可以使用 keytool 命令创建包含自签名证书的 keystore 文件:-

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 并指向 keystore :-

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

让我们创建一个 super 简单的 Spring Boot Controller 端点来测试:-

@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 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 对象注入(inject))。

SSLContext 类被注入(inject)到 SSLConnectionSocketFactory 对象中,该对象又被注入(inject)到 HttpClient 对象中,然后该对象又被注入(inject)到HttpComponentsClientHttpRequestFactory 对象。

此工厂对象最终被注入(inject)到 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.

关于spring-security - 如何配置 TestRestTemplate 以使用 keystore ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56378142/

相关文章:

java - 如何在android keystore 中存储私钥?

java - Android 中未找到 KeyGenerator AES 实现

android - keyStore rsa privateKeyEntry,公钥和私钥相等

c# - xUnit 的模拟服务

java - 在需要 Maven 预集成测试执行的 Eclipse 中运行 JUnit 集成测试?

java - 如何在 Spring 中映射请求参数?

grails - Grails Spring Security:如何为LdapAuthenticator使用自己的用户域?

ruby-on-rails - 如何在 spec_helper.rb 中为 Capybara Webkit 设置 ignore_ssl_errors 选项

java - 根据当前请求URI获取用户权限

Spring Social 记住我登录