【问题标题】:Access Https Rest Service using Spring RestTemplate使用 Spring RestTemplate 访问 Https Rest 服务
【发布时间】:2013-07-11 06:55:50
【问题描述】:

谁能提供一个代码示例来访问使用 Spring Rest 模板通过 HTTPS 保护的 REST 服务 URL?

我有证书、用户名和密码。基本身份验证在服务器端使用,我想创建一个可以使用提供的证书、用户名和密码(如果需要)连接到该服务器的客户端。

【问题讨论】:

    标签: spring rest https certificate resttemplate


    【解决方案1】:

    这是一个不推荐使用类或方法的解决方案: (Java 8 已获批准)

    CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
    
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    
    RestTemplate restTemplate = new RestTemplate(requestFactory);
    

    重要信息:使用 NoopH​​ostnameVerifier 存在安全风险

    【讨论】:

    • 任何时候忽略证书都是安全风险。我假设(希望?)OP 计划使用它进行测试
    【解决方案2】:

    您需要配置一个支持 SSL 的原始 HttpClient,如下所示:

    @Test
    public void givenAcceptingAllCertificatesUsing4_4_whenUsingRestTemplate_thenCorrect() 
    throws ClientProtocolException, IOException {
        CloseableHttpClient httpClient
          = HttpClients.custom()
            .setSSLHostnameVerifier(new NoopHostnameVerifier())
            .build();
        HttpComponentsClientHttpRequestFactory requestFactory 
          = new HttpComponentsClientHttpRequestFactory();
        requestFactory.setHttpClient(httpClient);
    
        ResponseEntity<String> response 
          = new RestTemplate(requestFactory).exchange(
          urlOverHttps, HttpMethod.GET, null, String.class);
        assertThat(response.getStatusCode().value(), equalTo(200));
    }
    

    from: Baeldung

    【讨论】:

      【解决方案3】:
      KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
      keyStore.load(new FileInputStream(new File(keyStoreFile)),
        keyStorePassword.toCharArray());
      
      SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
        new SSLContextBuilder()
          .loadTrustMaterial(null, new TrustSelfSignedStrategy())
          .loadKeyMaterial(keyStore, keyStorePassword.toCharArray())
          .build(),
          NoopHostnameVerifier.INSTANCE);
      
      HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(
        socketFactory).build();
      
      ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
        httpClient);
      RestTemplate restTemplate = new RestTemplate(requestFactory);
      MyRecord record = restTemplate.getForObject(uri, MyRecord.class);
      LOG.debug(record.toString());
      

      【讨论】:

      • 在 5 年春季有没有想出如何做同样的事情
      • 你如何在春季5实现这一目标?
      • 这是完全不安全的。没有服务器证书验证 (TrustSelfSignedStrategy),没有主机名验证 (NoopHostnameVerifier)。提供的关键材料用于客户端证书身份验证,但问题说它使用的是 HTTP Basic 身份验证。在这种情况下,从密钥库加载的内容根本不会被使用。
      【解决方案4】:

      从我这里得到一点。我在 spring-boot 微服务中使用了相互证书身份验证。以下对我有用,这里的重点是 keyManagerFactory.init(...)sslcontext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom()) 没有它们的代码行,至少对我来说,事情没有奏效。证书由 PKCS12 打包。

      @Value("${server.ssl.key-store-password}")
      private String keyStorePassword;
      @Value("${server.ssl.key-store-type}")
      private String keyStoreType;
      @Value("${server.ssl.key-store}")
      private Resource resource;
      
      private RestTemplate getRestTemplate() throws Exception {
          return new RestTemplate(clientHttpRequestFactory());
      }
      
      private ClientHttpRequestFactory clientHttpRequestFactory() throws Exception {
          return new HttpComponentsClientHttpRequestFactory(httpClient());
      }
      
      private HttpClient httpClient() throws Exception {
      
          KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
          KeyStore trustStore = KeyStore.getInstance(keyStoreType);
      
          if (resource.exists()) {
              InputStream inputStream = resource.getInputStream();
      
              try {
                  if (inputStream != null) {
                      trustStore.load(inputStream, keyStorePassword.toCharArray());
                      keyManagerFactory.init(trustStore, keyStorePassword.toCharArray());
                  }
              } finally {
                  if (inputStream != null) {
                      inputStream.close();
                  }
              }
          } else {
              throw new RuntimeException("Cannot find resource: " + resource.getFilename());
          }
      
          SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
          sslcontext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
          SSLConnectionSocketFactory sslConnectionSocketFactory =
                  new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1.2"}, null, getDefaultHostnameVerifier());
      
          return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
      }
      

      【讨论】:

      • 如果您将trustStore 重命名为keyStore 可能会更清楚,因为您将它用于密钥管理器,而不是信任管理器。
      【解决方案5】:

      这里有一些代码可以让你大致了解一下。

      您需要创建自定义ClientHttpRequestFactory 才能信任证书。 它看起来像这样:

      final ClientHttpRequestFactory clientHttpRequestFactory =
              new MyCustomClientHttpRequestFactory(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER, serverInfo);
          restTemplate.setRequestFactory(clientHttpRequestFactory);
      

      这是MyCustomClientHttpRequestFactory的实现:

      public class MyCustomClientHttpRequestFactory  extends SimpleClientHttpRequestFactory {
      
      private final HostnameVerifier hostNameVerifier;
      private final ServerInfo serverInfo;
      
      public MyCustomClientHttpRequestFactory (final HostnameVerifier hostNameVerifier,
          final ServerInfo serverInfo) {
          this.hostNameVerifier = hostNameVerifier;
          this.serverInfo = serverInfo;
      }
      
      @Override
      protected void prepareConnection(final HttpURLConnection connection, final String httpMethod)
          throws IOException {
          if (connection instanceof HttpsURLConnection) {
              ((HttpsURLConnection) connection).setHostnameVerifier(hostNameVerifier);
              ((HttpsURLConnection) connection).setSSLSocketFactory(initSSLContext()
                  .getSocketFactory());
          }
          super.prepareConnection(connection, httpMethod);
      }
      
      private SSLContext initSSLContext() {
          try {
              System.setProperty("https.protocols", "TLSv1");
      
              // Set ssl trust manager. Verify against our server thumbprint
              final SSLContext ctx = SSLContext.getInstance("TLSv1");
              final SslThumbprintVerifier verifier = new SslThumbprintVerifier(serverInfo);
              final ThumbprintTrustManager thumbPrintTrustManager =
                  new ThumbprintTrustManager(null, verifier);
              ctx.init(null, new TrustManager[] { thumbPrintTrustManager }, null);
              return ctx;
          } catch (final Exception ex) {
              LOGGER.error(
                  "An exception was thrown while trying to initialize HTTP security manager.", ex);
              return null;
          }
      }
      

      在这种情况下,我的serverInfo 对象包含服务器的指纹。 需要实现TrustManager接口才能得到 SslThumbprintVerifier 或您想要验证证书的任何其他方法(您也可以决定也始终返回 true)。

      org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER 允许所有主机名。 如果需要验证主机名, 你需要以不同的方式实现它。

      我不确定用户和密码以及您是如何实现的。 经常, 您需要将标题添加到名为 AuthorizationrestTemplate 具有如下所示的值:Base: &lt;encoded user+password&gt;user+password 必须是 Base64 编码的。

      【讨论】:

      • 我的很多代码都取自这里:stackoverflow.com/questions/15544116/…
      • 看起来很干净,除了 "System.setProperty("https.protocols", "TLSv1");"线。有没有办法不设置一些系统级的东西?我遇到了类似的问题,想将与证书相关的问题隔离到一个 bean。
      • @Ruslan - 哇,你用这个答案把我带回来了。不幸的是,那是很久以前的事了,从那时起我已经换了两个工作场所,所以我没有源代码,我不记得为什么我会这样做。我很确定有办法解决它,我会尝试看看是否可以找到另一种方法,如果可以,我会在这里发布。
      • 我已经找到了解决方案。让我用它发布另一个答案,因为它不是微不足道的。无论如何,谢谢。
      • 你最好加上SslThumbprintVerifierclass。
      【解决方案6】:

      这是我对类似问题的最终结果。这个想法与@Avi的答案相同,但我也想避免静态“System.setProperty("https.protocols", "TLSv1");",这样任何调整都不会影响系统。灵感来自这里的答案http://www.coderanch.com/t/637177/Security/Disabling-handshake-message-Java

      public class MyCustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
      
      @Override
      protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
          try {
              if (!(connection instanceof HttpsURLConnection)) {
                  throw new RuntimeException("An instance of HttpsURLConnection is expected");
              }
      
              HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
      
              TrustManager[] trustAllCerts = new TrustManager[]{
                      new X509TrustManager() {
                          public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                              return null;
                          }
      
                          public void checkClientTrusted(X509Certificate[] certs, String authType) {
                          }
      
                          public void checkServerTrusted(X509Certificate[] certs, String authType) {
                          }
      
                      }
              };
              SSLContext sslContext = SSLContext.getInstance("SSL");
              sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
              httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
      
              httpsConnection.setHostnameVerifier((hostname, session) -> true);
      
              super.prepareConnection(httpsConnection, httpMethod);
          } catch (Exception e) {
              throw Throwables.propagate(e);
          }
      }
      
      /**
       * We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
       * see http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html (Java 8 section)
       */
      private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
      
          private final SSLSocketFactory delegate;
      
          public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
              this.delegate = delegate;
          }
      
          @Override
          public String[] getDefaultCipherSuites() {
              return delegate.getDefaultCipherSuites();
          }
      
          @Override
          public String[] getSupportedCipherSuites() {
              return delegate.getSupportedCipherSuites();
          }
      
          @Override
          public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose) throws IOException {
              final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
              return overrideProtocol(underlyingSocket);
          }
      
          @Override
          public Socket createSocket(final String host, final int port) throws IOException {
              final Socket underlyingSocket = delegate.createSocket(host, port);
              return overrideProtocol(underlyingSocket);
          }
      
          @Override
          public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
              final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
              return overrideProtocol(underlyingSocket);
          }
      
          @Override
          public Socket createSocket(final InetAddress host, final int port) throws IOException {
              final Socket underlyingSocket = delegate.createSocket(host, port);
              return overrideProtocol(underlyingSocket);
          }
      
          @Override
          public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
              final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
              return overrideProtocol(underlyingSocket);
          }
      
          private Socket overrideProtocol(final Socket socket) {
              if (!(socket instanceof SSLSocket)) {
                  throw new RuntimeException("An instance of SSLSocket is expected");
              }
              ((SSLSocket) socket).setEnabledProtocols(new String[] {"SSLv3"});
              return socket;
          }
      }
      }
      

      【讨论】:

      • 不安全,因为没有证书验证和主机名验证。
      猜你喜欢
      • 2019-01-26
      • 2017-09-27
      • 1970-01-01
      • 1970-01-01
      • 2017-07-08
      • 2015-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多