【问题标题】:Spring Boot SSL ClientSpring Boot SSL 客户端
【发布时间】:2015-08-26 12:40:36
【问题描述】:

我是 Spring Boot 的新手。到目前为止,我很享受。我开发了一个演示 SSL REST Web 服务器,可以正确处理相互 X.509 证书身份验证。使用带有自签名客户端和服务器证书的 IE 浏览器,我测试了演示 rest web 服务器工作正常——服务器和浏览器都成功地交换和验证了彼此的证书。

我在查找 SSL 客户端示例时遇到了问题,该示例显示了如何包含客户端证书并颁发 https。有人有一个简单的 REST 客户端示例来展示如何使用我的 ssl 服务器吗?

最好的问候, 史蒂夫·曼斯菲尔德

【问题讨论】:

  • 您心目中的客户是什么? Java(使用 Spring)?还是有什么不同?
  • Spring 最好,但 Java 也可以。
  • 嗨,史蒂夫,我偶然发现了这个问题,想知道为什么需要包含 SSL 客户端代码?
  • 可能无法回答你的问题,但我开始使用 OkHttp 客户端,它就像标准 java 一样工作

标签: spring rest ssl spring-boot ssl-certificate


【解决方案1】:

我知道为时已晚,但这是适合我的代码。

@SpringBootApplication
public class Application {

    private static final Logger log = LoggerFactory.getLogger(Application.class);

    public static void main(String args[]) {
            makeWebServiceCall();
    }
    
    public static void makeWebServiceCall() {
        TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;

        SSLContext sslContext;
        ResponseEntity<String> response = null;
        try {
            sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy)
                    .build();

            SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

            CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();

            HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();

            requestFactory.setHttpClient(httpClient);

            RestTemplate restTemplate = new RestTemplate(requestFactory);
            
            StringBuffer plainCreds = new StringBuffer();
            plainCreds.append("username");
            plainCreds.append(":");
            plainCreds.append("password");
            byte[] plainCredsBytes = plainCreds.toString().getBytes();
            byte[] base64CredsBytes = Base64.getEncoder().encode(plainCredsBytes);
            String userBase64Credentials = new String(base64CredsBytes);
            

            HttpHeaders headers = new HttpHeaders();
            headers.add("Authorization", "Basic " + userBase64Credentials);
            headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
            headers.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity entity = new HttpEntity<>(headers);

            String url = "https:restUrl";
            
            response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
            
            if(response.getStatusCodeValue() == 200) {
                log.info("Success! Further processing based on the need");
            } else {
                log.info("****************Status code received: " + response.getStatusCodeValue() + ".************************");
            }

        } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
            log.error("Exception occured. Here are the exception details: ", e);
        } catch(HttpClientErrorException e) {
            if(e.getRawStatusCode() == 403) {
                log.info("****************Status code received: " + e.getRawStatusCode() + ". You do not have access to the requested resource.************************");
                
            } else if(e.getRawStatusCode() == 404) {
                log.info("****************Status code received: " + e.getRawStatusCode() + ". Resource does not exist(or) the service is not up.************************");

            } else if(e.getRawStatusCode() == 400) {
                log.info("****************Status code received: " + e.getRawStatusCode() + ". Bad Request.************************");

            } else {
                log.info("****************Status code received: " + e.getRawStatusCode() + ".************************");
                
            }

           log.info("****************Response body: " + e.getResponseBodyAsString() + "************************");
        }
    }
 }

这里是 maven 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.springframework</groupId>
<artifactId>gs-consuming-rest</artifactId>
<version>0.1.0</version>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.2.RELEASE</version>
</parent>

<properties>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
    
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.3</version>
</dependency>

        
        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    <version>4.4.6</version>
</dependency>
    
    
</dependencies>


<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

【讨论】:

    【解决方案2】:

    其他方式来做到这一点。注入 keyStoreLocation 和 keyStorePassword 的值

    @Configuration
    public class SampleSSLClient extends RestTemplate{
    
        /** The key store password. */
        private  String keyStorePassword;
      
        /** The key store location. */
        private  String keyStoreLocation;
      
        /** The rest template. */
        @Autowired
        private RestTemplate restTemplate;
        
         /** The http components client http request factory. */
        private HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory;
    
        /**
         * Instantiates a new custom rest template.
         */
        public CustomRestTemplate() {
            super();
        }
        
        public CustomRestTemplate(RestTemplate restTemplate){
            this.restTemplate = getRestTemplate();
        }
            
        /**
         * Rest template.
         *
         * @return the rest template
         */
        public RestTemplate getRestTemplate()  {
            if (null == httpComponentsClientHttpRequestFactory) {
                httpComponentsClientHttpRequestFactory = loadCert();
                restTemplate.setRequestFactory(httpComponentsClientHttpRequestFactory);
            }
            return restTemplate;
        }
    
        /**
         * Load cert.
         *
         * @return the http components client http request factory
         */
        private HttpComponentsClientHttpRequestFactory loadCert()  {
            try {
                char[] keypass = keyStorePassword.toCharArray();
                SSLContext sslContext = SSLContextBuilder.create()
                        .loadKeyMaterial(getkeyStore(keyStoreLocation, keypass), keypass)
                        .loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
                HttpClient client = HttpClients.custom().setSSLContext(sslContext).build();
                httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(client);
                httpComponentsClientHttpRequestFactory.setConnectTimeout(5000);
                httpComponentsClientHttpRequestFactory.setReadTimeout(30000);
            } catch (Exception ex) {
                LOGGER.error(MessageFormat.format("Some Error", ex.getMessage()), ex);
                
            }
            return httpComponentsClientHttpRequestFactory;
        }
    
         /**
         * Key store.
         *
         * @param storePath the store path
         * @param password the password
         * @return the key store
         */
        private KeyStore getkeyStore(String storePath, char[] password) {
            KeyStore keyStore;
            try {
                keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                File key = ResourceUtils.getFile(storePath);
                try (InputStream in = new FileInputStream(key)) {
                    keyStore.load(in, password);
                }
            }catch (Exception ex) {
                LOGGER.error(MessageFormat.format("Some Error", ex.getMessage()), ex);
            
            }
            return keyStore;
        }
    
        /**
         * Sets the key store password.
         *
         * @param keyStorePassword the new key store password
         */
        public void setKeyStorePassword(String keyStorePassword) {
            this.keyStorePassword = keyStorePassword;
        }
    
        /**
         * Sets the key store location.
         *
         * @param keyStoreLocation the new key store location
         */
        public void setKeyStoreLocation(String keyStoreLocation) {
            this.keyStoreLocation = keyStoreLocation;
        }
    
        /**
         * Sets the rest template.
         *
         * @param restTemplate the new rest template
         */
        public void setRestTemplate(RestTemplate restTemplate) {
            this.restTemplate = restTemplate;
        }
    }
    

    【讨论】:

    • 吞下异常并不是最好的办法。
    • 这就是所谓的“处理期望”@jannis
    【解决方案3】:

    user1707141 的示例对我不起作用,skmansfield 似乎取决于特定文件,这些文件与 Spring Boot / Maven 不约定。 Andy Wilkinson 的回答也使用了构造函数 SSLConnectionSocketFactory,它在 Apache httpclient 4.4+ 中已被弃用,而且看起来也很复杂。

    所以我创建了一个示例项目,应该在此处显示所有内容100% 可理解https://github.com/jonashackt/spring-boot-rest-clientcertificate

    除了在您的测试类中正常使用带有@Autowired 的 RestTemplate 之外,请务必像这样配置您的 RestTemplate:

    package de.jonashackt.restexamples;
    
    import org.apache.http.client.HttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.ssl.SSLContextBuilder;
    import org.springframework.boot.web.client.RestTemplateBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
    import org.springframework.util.ResourceUtils;
    import org.springframework.web.client.RestTemplate;
    
    import javax.net.ssl.SSLContext;
    
    @Configuration
    public class RestClientCertTestConfiguration {
    
        private String allPassword = "allpassword";
    
        @Bean
        public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
    
            SSLContext sslContext = SSLContextBuilder
                    .create()
                    .loadKeyMaterial(ResourceUtils.getFile("classpath:keystore.jks"), allPassword.toCharArray(), allPassword.toCharArray())
                    .loadTrustMaterial(ResourceUtils.getFile("classpath:truststore.jks"), allPassword.toCharArray())
                    .build();
    
            HttpClient client = HttpClients.custom()
                    .setSSLContext(sslContext)
                    .build();
    
            return builder
                    .requestFactory(() -> new HttpComponentsClientHttpRequestFactory(client))
                    .build();
        }
    }
    

    【讨论】:

    • 无法自动装配,找不到“RestTemplateBuilder”类型的bean
    【解决方案4】:

    这对我有用:

    TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
    javax.net.ssl.SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
            .loadTrustMaterial(null, acceptingTrustStrategy).build();
    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
    CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    RestTemplate restTemplate = new RestTemplate(requestFactory);
    

    【讨论】:

      【解决方案5】:

      我无法让 Andy 提交的上述客户端工作。我不断收到错误消息,说“localhost!= clientname”。无论如何,我让它正常工作。

       import java.io.IOException;
      
       import org.apache.commons.httpclient.HttpClient;
       import org.apache.commons.httpclient.HttpException;
       import org.apache.commons.httpclient.URI;
       import org.apache.commons.httpclient.methods.GetMethod;
      
       public class SSLClient {
      
            static
              {
                System.setProperty("javax.net.ssl.trustStore","c:/apachekeys/client1.jks");
                System.setProperty("javax.net.ssl.trustStorePassword", "password");
                System.setProperty("javax.net.ssl.keyStore", "c:/apachekeys/client1.jks");
                System.setProperty("javax.net.ssl.keyStorePassword", "password");
             }
      
           public static void main(String[] args) throws HttpException, IOException {
      
               HttpClient client = new HttpClient();
               GetMethod method = new GetMethod();
               method.setURI(new URI("https://localhost:8443/restserver", false));
               client.executeMethod(method);
      
               System.out.println(method.getResponseBodyAsString());
      
           }
      
       }
      

      【讨论】:

      • 这是系统范围的,它甚至适用于jdbc连接,不推荐
      • 这在您的服务器需要使用数字证书进行客户端身份验证时很有用。
      【解决方案6】:

      鉴于您使用的是 Spring,下面的示例展示了如何使用配置了客户端证书的 Spring 的 RestTemplate 和 Apache 的 HttpClient 并信任来自服务器的自签名证书:

      KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
      keyStore.load(new FileInputStream(new File("keystore.jks")),
              "secret".toCharArray());
      SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
              new SSLContextBuilder()
                      .loadTrustMaterial(null, new TrustSelfSignedStrategy())
                      .loadKeyMaterial(keyStore, "password".toCharArray()).build());
      HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
      ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
              httpClient);
      RestTemplate restTemplate = new RestTemplate(requestFactory);
      ResponseEntity<String> response = restTemplate.getForEntity(
              "https://localhost:8443", String.class);
      

      【讨论】:

      • 编译器对 SSLConnectionSocketFactory 大吼大叫。我玩了几个小时,但没有运气。可以看看修一下吗?
      • 关于代码的另一个问题。您在密钥库中有“秘密”,然后在 loadkeymaterial 中有“密码”。什么是秘密?
      • 我认为 SSLConnectionSocketFactory 没有什么可修复的。这是它的javadoc,如果有帮助的话。 secret 是整个密钥库的密码。 password 是密钥库中密钥的密码。
      • 谢谢安迪...我找到了问题,我正在解决它。由于某种原因,spring boot 没有下载必要的 jars。现在正在努力寻找他们。明天第一件事应该有工作。
      • 嗨,安迪,你知道我在哪里可以找到 SSLConnectionSocketFactory 的 jar 吗?
      猜你喜欢
      • 2022-11-28
      • 2019-06-29
      • 2020-05-19
      • 2016-09-28
      • 2018-08-22
      • 1970-01-01
      • 1970-01-01
      • 2023-01-31
      • 1970-01-01
      相关资源
      最近更新 更多