【问题标题】:How to manually configurate SSL Handshake in Spring boot如何在 Spring Boot 中手动配置 SSL 握手
【发布时间】:2019-09-10 15:19:20
【问题描述】:

我正在设置一个应该与其他受信任的服务器通信的服务器,并且我在 spring boot 中使用 SSL Handshake 和 apache tomcat。

我成功地制作了 2 路 SSL,但是,如果我添加确实在信任存储中生成证书的根 CA,它会自动信任来自该 CA 的每个孩子。我希望它检查该特定证书是否在信任库中,而不仅仅是来自同一个父级。

第二台服务器的应用程序属性相同,只是 KeyStore 和 Truststore 不同。

Application.properties

server.ssl.key-store=classpath:booker.p12
server.ssl.key-store-password=pass
server.ssl.key-password=pass
server.ssl.key-alies=booker
server.ssl.trust-store=classpath:bookerTrust.p12
server.ssl.trust-store-password=pass
server.ssl.trust-store-type = PKCS12
server.ssl.client-auth=need
server.port=8111

main.java 中的 RestTemplate 设置

    @Bean
    public RestTemplate restTemplate() throws Exception {
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
        restTemplate.setErrorHandler(
                new DefaultResponseErrorHandler() {
                    @Override
                    protected boolean hasError(HttpStatus statusCode) {
                        return false;
                    }
                });

        return restTemplate;
    }

    private ClientHttpRequestFactory clientHttpRequestFactory() throws Exception {
        return new HttpComponentsClientHttpRequestFactory(httpClient());
    }

    private HttpClient httpClient() throws Exception {
        // Load our keystore and truststore containing certificates that we trust.
        SSLContext sslcontext =
                SSLContexts.custom().loadTrustMaterial(trustResource.getFile(), trustStorePassword.toCharArray())
                        .loadKeyMaterial(keyStore.getFile(), keyStorePassword.toCharArray(),
                                keyPassword.toCharArray()).build();
        SSLConnectionSocketFactory sslConnectionSocketFactory =
                new SSLConnectionSocketFactory(sslcontext, new HostCustomVerifer(trustResource,trustStorePassword,trustStoreType));
        return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
    }
}

控制器中的通信:

@RestController
@RequestMapping(value = "/server1")
public class ClientController {

    @Autowired
    RestTemplate restTemplate;

    @RequestMapping(value = "/data", method = RequestMethod.GET)
    ResponseEntity<?> getMessage(ServletRequest request) {
    return ResponseEntity.ok("Server1 successfully called!");
    }


    @RequestMapping(value = "/sdata", method = RequestMethod.GET)
    public ResponseEntity<String> getMsData() {
        try {
            String msEndpoint = https://localhost:8111/api/server2/data";
            return new ResponseEntity<String>( restTemplate.getForObject(new URI(msEndpoint), String.class), HttpStatus.OK) ;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return new ResponseEntity<String>("Exception occurred.. so, returning default data", HttpStatus.BAD_GATEWAY);
    }


}

我预计这种情况会失败,但它会成功:

Server1:信任 RootCA、Server1、Server2

Server2:信任 RootCA、Server2

Server1 开始与 Server2 通信并以某种方式成功

我预计它会失败,因为 Server2 不信任 Server1

【问题讨论】:

    标签: java spring apache ssl handshake


    【解决方案1】:

    然而,这不是最好的解决方案,如果双方都不能信任,它可以满足我无法沟通的需求。 我实现了 javax.net.ssl.HostnameVerifier 并制作了我的 CustomNameVerifier,并在制作 RestTemplate 时将其放入了 SSLConnectionSocketFactory。后面的代码

    Bean 代码应该放在 main (With @SpringBootApplication)

    @Bean
    public RestTemplate template() throws Exception{
    
        RestTemplate restTemplate = new RestTemplate();
    
        KeyStore keyStore;
        KeyStore trustStore;
        HttpComponentsClientHttpRequestFactory requestFactory = null;
        try {
            keyStore = KeyStore.getInstance(keyStoreType);
            ClassPathResource classPathResource = new ClassPathResource(keyStoreLocation.getFilename());
            InputStream inputStream = classPathResource.getInputStream();
            keyStore.load(inputStream, keyStorePassword.toCharArray());
    
            trustStore = KeyStore.getInstance(trustStoreType);
            ClassPathResource classPathResourceTrust = new ClassPathResource(trustStoreLocation.getFilename());
            InputStream trustInput = classPathResourceTrust.getInputStream();
            trustStore.load(trustInput, trustStorePassword.toCharArray());
    
            javax.net.ssl.SSLContext sslContext = SSLContextBuilder.create()
                    .loadKeyMaterial(keyStore, keyStorePassword.toCharArray())
                    .loadTrustMaterial(trustStore, null).setProtocol("TLS")
                    .build();
            SSLConnectionSocketFactory sslConnectionSocketFactory =
                    new SSLConnectionSocketFactory(sslContext,
                    new HostCustomVerifer(trustStoreLocation,trustStorePassword,trustStoreType));
    
            HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory)
                    .setMaxConnTotal(Integer.valueOf(200))
                    .setMaxConnPerRoute(Integer.valueOf(200))
                    .build();
    
            requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
            requestFactory.setReadTimeout(Integer.valueOf(10000));
            requestFactory.setConnectTimeout(Integer.valueOf(10000));
    
            restTemplate.setRequestFactory(requestFactory);
        } catch (Exception exception) {
            System.out.println("Exception Occured while creating restTemplate "+exception);
            exception.printStackTrace();
        }
        return restTemplate;
    }
    

    CustomNameVerifier 如下:(即使您收到不同的错误消息(它应该是您因名称验证失败而获得的不受信任的证书。

    public class HostCustomVerifer implements HostnameVerifier {
    
    private String trustStorePassword;
    private String trustResource;
    private String trustType;
    
    @Autowired
    public HostCustomVerifer(@Value("${server.ssl.trust-store}") Resource trustResource,@Value("${server.ssl.trust-store-password}") String trustStorePassword,@Value("${server.ssl.trust-store-type}") String trustType) {
        this.trustStorePassword = trustStorePassword;
        this.trustResource = trustResource.getFilename();
        this.trustType = trustType;
    }
    
    @Override
    public boolean verify(String hostName, SSLSession sslSession) {
        try {
            X509Certificate list[] =  (X509Certificate[]) sslSession.getPeerCertificates();
            int size = list.length;
            KeyStore trustStore = KeyStore.getInstance(trustType);
            ClassPathResource classPathResourceTrust = new ClassPathResource(trustResource);
            InputStream trustInput = classPathResourceTrust.getInputStream();
            trustStore.load(trustInput, trustStorePassword.toCharArray());  
            for (X509Certificate x509Certificate : list) {
                ArrayList<String> content = Collections.list(trustStore.aliases());
                for (String alias : content) {
                    X509Certificate cert = (X509Certificate) trustStore.getCertificate(alias);
                    boolean isSelfSigned = cert.getIssuerDN().equals(cert.getSubjectDN());
                    if(cert.equals(x509Certificate) && (!isSelfSigned || size==1))
                        return true;
                }
            }
        } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
            e.printStackTrace();
        }
        return false;
    }
    

    }

    如果有人突然发现这个帖子并找到了更好的方法,你可以通知我。

    【讨论】:

      猜你喜欢
      • 2020-09-01
      • 1970-01-01
      • 2018-04-14
      • 1970-01-01
      • 2018-10-25
      • 1970-01-01
      • 1970-01-01
      • 2019-07-01
      • 2021-04-28
      相关资源
      最近更新 更多