java客戶端驗證https連接(忽略證書驗證和證書驗證兩種方式)


首先根據如下操作生成證書,配置springboot https,生成一個簡單的https web服務

https://www.cnblogs.com/qq931399960/p/11889349.html

驗證客戶端pom依賴

</dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>4.5.10</version>
            </dependency>
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpcore</artifactId>
                <version>4.4.12</version>
            </dependency>

httpclient和httpcore版本要對應,否則可能會出現異常

驗證方式包括跳過證書驗證,也即是添加信任,就像瀏覽器訪問自簽名https服務時,頁面會給出提示“您的鏈接不是私密連接”,點擊了高級,繼續前往即是對該服務添加了信任,可以繼續訪問該網站服務,另外一種方式就是通過服務器證書來驗證,下面就直接上代碼

跳過證書驗證方式

package com.demo.bootdemo;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class SkipVerifyRestTemplateBuilder {

	private Logger logger = LoggerFactory.getLogger(SkipVerifyRestTemplateBuilder.class);

	// 初始化ssl resttemplate
	@Bean("skipVerifyRestTemplate")
	public RestTemplate skipVerifyRestTemplate() {
		RestTemplate rest = new RestTemplate();

		SSLConnectionSocketFactory buildSSLSocketFactory = null;
		try {
			buildSSLSocketFactory = this.buildSSLSocketFactory();
		} catch (Exception e) {
			logger.error("", e);
		}

		HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
				HttpClients.custom().setSSLSocketFactory(buildSSLSocketFactory).build());

		factory.setConnectionRequestTimeout(1000);
		factory.setConnectTimeout(1000);
		
		rest.setRequestFactory(factory);
		return rest;
	}

	private SSLConnectionSocketFactory buildSSLSocketFactory() throws Exception {
		SSLContext sslContext = SSLContext.getInstance("SSL");
		// 設置信任證書(繞過TrustStore驗證)
		sslContext.init(null, new TrustManager[] { new AuthX509TrustManager() }, null);
		HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

		SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
				new String[] { "TLSv1" }, null, new HostnameVerifier() {
					// hostname,默認返回true,不驗證hostname
					@Override
					public boolean verify(String urlHostName, SSLSession session) {
						return true;
					}
				});
		return sslConnectionSocketFactory;
	}

	private class AuthX509TrustManager implements TrustManager, X509TrustManager {
		public X509Certificate[] getAcceptedIssuers() {
			return null;
		}

		public void checkServerTrusted(X509Certificate[] certs, String authType)
				throws java.security.cert.CertificateException {
			return;
		}

		public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
			return;
		}
	}
}

第二種跳過證書驗證方式

package com.demo.bootdemo;

import java.io.IOException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class SecondSkipVerifyRestTemplateBuilder {

    @Bean("secondSkipRestTemplate")
    public RestTemplate verifyCaRestTemplate() {
        RestTemplate rest = new RestTemplate();
        SSLConnectionSocketFactory ssLSocketFactory = null;
        try {
            ssLSocketFactory = sslFactory("PKCS12", "abc123");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(
                HttpClients.custom().setSSLSocketFactory(ssLSocketFactory).build());
        // 設置傳遞數據超時時長
        httpRequestFactory.setReadTimeout(1000);

        rest.setRequestFactory(httpRequestFactory);

        // 如果返回的數據非json則可能需要添加對應httpmessageconverter
        // Jaxb2RootElementHttpMessageConverter converter = new
        // Jaxb2RootElementHttpMessageConverter();
        //
        // List<MediaType> mediaTypeList = new ArrayList<>();
        // mediaTypeList.addAll(converter.getSupportedMediaTypes());
        // mediaTypeList.add(MediaType.TEXT_HTML);
        // converter.setSupportedMediaTypes(mediaTypeList);
        //
        // List<HttpMessageConverter<?>> list = new ArrayList<>();
        // list.add(converter);
        // rest.setMessageConverters(list);

        return rest;

    }

    public SSLConnectionSocketFactory sslFactory(String keyStoreType, String keyPassword) {
        SSLConnectionSocketFactory sslConnectionSocketFactory = null;
        try {
            SSLContext sslcontext = SSLContexts.custom()
                    // //忽略掉對服務器端證書的校驗
                    .loadTrustMaterial(new TrustStrategy() {
                        @Override
                        public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                            return true;
                        }
                    }).build();
            sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null,
                    SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sslConnectionSocketFactory;
    }

}

根據證書驗證

package com.demo.bootdemo;

import java.io.IOException;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class VerifyCaRestTemplateBuilder {

    private Logger logger = LoggerFactory.getLogger(VerifyCaRestTemplateBuilder.class);

    @Value("classpath:cert.p12")
    private Resource certFile;

    @Bean("verifyCaRestTemplate")
    public RestTemplate verifyCaRestTemplate() {
        RestTemplate rest = new RestTemplate();

        SSLConnectionSocketFactory ssLSocketFactory = null;
        try {
            ssLSocketFactory = sslFactory("PKCS12", "abc123");
        } catch (Exception e) {
            logger.error("", e);
        }

        HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(
                HttpClients.custom().setSSLSocketFactory(ssLSocketFactory).build());
        // 設置傳遞數據超時時長
        httpRequestFactory.setReadTimeout(1000);

        rest.setRequestFactory(httpRequestFactory);

        // 如果返回的數據非json則可能需要添加對應httpmessageconverter
        // Jaxb2RootElementHttpMessageConverter converter = new
        // Jaxb2RootElementHttpMessageConverter();
        //
        // List<MediaType> mediaTypeList = new ArrayList<>();
        // mediaTypeList.addAll(converter.getSupportedMediaTypes());
        // mediaTypeList.add(MediaType.TEXT_HTML);
        // converter.setSupportedMediaTypes(mediaTypeList);
        //
        // List<HttpMessageConverter<?>> list = new ArrayList<>();
        // list.add(converter);
        // rest.setMessageConverters(list);

        return rest;

    }

    public SSLConnectionSocketFactory sslFactory(String keyStoreType, String keyPassword) {
        SSLConnectionSocketFactory sslConnectionSocketFactory = null;
        try {
            KeyStore keyStore = null;
            try {
                keyStore = KeyStore.getInstance(keyStoreType);
                keyStore.load(certFile.getInputStream(), keyPassword.toCharArray());
            } catch (IOException e) {
                logger.error("", e);
            }

            HostnameVerifier hv = new HostnameVerifier() {
                @Override
                public boolean verify(String urlHostName, SSLSession session) {
                    // 如果需要驗證https域名,可以在該處做判斷,如果訪問的hostname與判斷不一致,則會出現如下異常
                    // if("localhost".equals(urlHostName)) {
                    // return true;
                    // }else {
                    // return false;
                    // }
                    // 此處不校驗hostname,接收所有hostname,只是用於測試。
                    return true;
                }
            };

            SSLContext sslcontext = SSLContexts.custom()
                    .loadTrustMaterial(certFile.getFile(), keyPassword.toCharArray(), new TrustSelfSignedStrategy())
                    .loadKeyMaterial(keyStore, keyPassword.toCharArray()).build();
            sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, hv);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return sslConnectionSocketFactory;
    }

}

 注:在上述HostnameVerifier 中,可以驗證hostname有效性,如果無效,返回fase,則會出現類似以下異常

javax.net.ssl.SSLPeerUnverifiedException: Certificate for <localhost> doesn't match any of the subject alternative names: []

測試controller

package com.demo.bootdemo;

import javax.annotation.Resource;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

@Controller
public class HttpsSendController {

    @Resource(name = "skipVerifyRestTemplate")
    private RestTemplate skipVerifyRestTemplate;

    @Resource(name = "verifyCaRestTemplate")
    private RestTemplate verifyCaRestTemplate;

    @Resource(name = "secondSkipRestTemplate")
    private RestTemplate secondSkipRestTemplate;

    @RequestMapping("/skip")
    @ResponseBody
    public String skipVerifyCert() {
        ResponseEntity<String> forEntity = skipVerifyRestTemplate.getForEntity("https://127.0.0.1:8443/test",
                String.class, new Object[] {});
        return forEntity.getBody();
    }

    @RequestMapping("/secondskip")
    @ResponseBody
    public String secondSkipVerifyCert() {
        ResponseEntity<String> forEntity = skipVerifyRestTemplate.getForEntity("https://127.0.0.1:8443/test",
                String.class, new Object[] {});
        return forEntity.getBody();
    }

    @RequestMapping("/verify")
    @ResponseBody
    public String verifyCert() {
        ResponseEntity<String> forEntity = verifyCaRestTemplate.getForEntity("https://127.0.0.1:8443/test",
                String.class, new Object[] {});
        return forEntity.getBody();
    }

}

可分別訪問當前客戶端的skip、secondskip、verify驗證結果

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM