nginx + https配置和部署

获得免费证书的网站:https://wosign.com/

示例:https://freessl.wosign.com/

介绍:http://www.wosign.com/DVSSL/DV_KuaiSSL_Free.htm

申请地址:https://buy.wosign.com/free/

Nginx SSL 证书部署指南:http://www.wosign.com/Docdownload/index.htm

http://www.wosign.com/Docdownload/Nginx%20SSL%E8%AF%81%E4%B9%A6%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97.pdf

另一个可以免费申请证书的站点(可以试下):https://www.startssl.com/

https://globalsign.ssllabs.com/analyze.html 这个网址可以去分析网址的SSL配置健康状况

我用本机绑定hosts:127.0.0.1 caiya.me,使用wosign获取免费证书进行测试。

nginx配置(80端口跳转到443端口):

server {
    listen 80;
    server_name caiya.me;
    rewrite ^(.*) https://$server_name$1 permanent;
}
server {
    listen 443;
    server_name caiya.me;
    ssl on;
    ssl_certificate sslkey/1_caiya.me_bundle.crt;#(证书公钥)
    ssl_certificate_key sslkey/2_caiya.me.key;#(证书私钥)
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ALL:!DH:!EXPORT:!RC4:+HIGH:+MEDIUM:-LOW:!aNULL:!eNULL;
    ssl_prefer_server_ciphers on;
    location / {
        root html;
        index index.html index.htm;
    }
}

在我使用免费证书之前先自签了一个无效证书,使用wget获取数据流的时候异常如下:

$ wget https://caiya.me
--2016-02-16 16:29:33--  https://caiya.me
Resolving caiya.me... 127.0.0.1
Connecting to caiya.me|127.0.0.1|:443... connected.
ERROR: cannot verify caiya.me's certificate, issued by '[email protected],CN=caiya.me,OU=AAA Tech,O=BBB Tech,L=Hangzhou,ST=Zhejiang,C=CN':
  Self-signed certificate encountered.
To connect to caiya.me insecurely, use `--no-check-certificate'.

那根据提示加个参数--no-check-certificate

wget --no-check-certificate https://caiya.me

就跳过验证了。。

这种异常情况还适用于证书的不完整性,中间证书丢失!!从浏览器地址栏看不出异样,还是绿色,但是wget或其他形式的请求就有问题了。

使用免费证书签名后,用java模拟http(s)请求时又发生如下异常(jdk版本1.7):

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
	at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
	at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1904)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:279)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:273)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1446)
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:209)
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:901)
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:837)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1023)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1332)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1359)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1343)
	at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:153)
	at com.maijia.order.web.utils.HttpRequestUtils.getResponseContent(HttpRequestUtils.java:84)
	at com.maijia.trade.AppTest.testHttps(AppTest.java:60)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at junit.framework.TestCase.runTest(TestCase.java:168)
	at junit.framework.TestCase.runBare(TestCase.java:134)
	at junit.framework.TestResult$1.protect(TestResult.java:110)
	at junit.framework.TestResult.runProtected(TestResult.java:128)
	at junit.framework.TestResult.run(TestResult.java:113)
	at junit.framework.TestCase.run(TestCase.java:124)
	at junit.framework.TestSuite.runTest(TestSuite.java:243)
	at junit.framework.TestSuite.run(TestSuite.java:238)
	at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:83)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)
	at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
	at sun.security.validator.Validator.validate(Validator.java:260)
	at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1428)
	... 29 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:196)
	at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:268)
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)
	... 35 more

而后发现jdk自带的证书中没有要请求的域名的认证信息时就报错,而同样的方式请求taobao、baidu则不会出现问题。

不同的jdk版本自带证书不完全相同,而且自带的一般都是高级证书。

于是有以下几种方式解决此问题:

1、绕过证书验证(需要指明,不同的请求方式、版本等可能需要不同的实现方式)

/**
     * 发送HTTP_POST_SSL请求
     * @see 1)该方法会自动关闭连接,释放资源
     * @see 2)该方法亦可处理普通的HTTP_POST请求
     * @see 3)当处理HTTP_POST_SSL请求时,默认请求的是对方443端口,除非reqURL参数中指明了SSL端口
     * @see 4)方法内设置了连接和读取超时时间,单位为毫秒,超时或发生其它异常时方法会自动返回"通信失败"字符串
     * @see 5)请求参数含中文等特殊字符时,可直接传入本方法,并指明其编码字符集encodeCharset参数,方法内部会自动对其转码
     * @see 6)方法内部会自动注册443作为SSL端口,若实际使用中reqURL指定的SSL端口非443,可自行尝试更改方法内部注册的SSL端口
     * @see 7)该方法在解码响应报文时所采用的编码,取自响应消息头中的[Content-Type:text/html; charset=GBK]的charset值
    // * @see   若响应消息头中未指定Content-Type属性,则会使用HttpClient内部默认的ISO-8859-1
     * @param reqURL        请求地址
     * @param params        请求参数
     * @param encodeCharset 编码字符集,编码请求数据时用之,当其为null时,则取HttpClient内部默认的ISO-8859-1编码请求参数
     * @return 远程主机响应正文
     */
    public static String sendPostSSLRequest(String reqURL, Map<String, String> params, String encodeCharset){
        String responseContent = "通信失败";
        HttpClient httpClient = new DefaultHttpClient();
        httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000);
        httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 20000);
        //创建TrustManager()
        //用于解决javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
        X509TrustManager trustManager = new X509TrustManager(){
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
            @Override
            public X509Certificate[] getAcceptedIssuers() {return null;}
        };
        //创建HostnameVerifier
        //用于解决javax.net.ssl.SSLException: hostname in certificate didn't match: <123.125.97.66> != <123.125.97.241>
        X509HostnameVerifier hostnameVerifier = new X509HostnameVerifier(){
            @Override
            public void verify(String host, SSLSocket ssl) throws IOException {}
            @Override
            public void verify(String host, X509Certificate cert) throws SSLException {}
            @Override
            public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {}
            @Override
            public boolean verify(String arg0, SSLSession arg1) {return true;}
        };
        try {
            //TLS1.0与SSL3.0基本上没有太大的差别,可粗略理解为TLS是SSL的继承者,但它们使用的是相同的SSLContext
            SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS);
            //使用TrustManager来初始化该上下文,TrustManager只是被SSL的Socket所使用
            sslContext.init(null, new TrustManager[]{trustManager}, null);
            //创建SSLSocketFactory
            SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, hostnameVerifier);
            //通过SchemeRegistry将SSLSocketFactory注册到HttpClient上
            httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory));
            //创建HttpPost
//            HttpPost httpPost = new HttpPost(reqURL);
            HttpGet httpGet = new HttpGet(reqURL);
            //由于下面使用的是new UrlEncodedFormEntity(....),所以这里不需要手工指定CONTENT_TYPE为application/x-www-form-urlencoded
            //因为在查看了HttpClient的源码后发现,UrlEncodedFormEntity所采用的默认CONTENT_TYPE就是application/x-www-form-urlencoded
            //httpPost.setHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded; charset=" + encodeCharset);
            //构建POST请求的表单参数
            if(null != params){
                List<NameValuePair> formParams = new ArrayList<NameValuePair>();
                for(Map.Entry<String,String> entry : params.entrySet()){
                    formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
//                httpPost.setEntity(new UrlEncodedFormEntity(formParams, encodeCharset));
            }
            HttpResponse response = httpClient.execute(httpGet);
            HttpEntity entity = response.getEntity();
            if (null != entity) {
                responseContent = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset());
                EntityUtils.consume(entity);
            }
        } catch (ConnectTimeoutException cte){
           logger.error("请求通信[" + reqURL + "]时连接超时,堆栈轨迹如下", cte);
        } catch (SocketTimeoutException ste){
            logger.error("请求通信[" + reqURL + "]时读取超时,堆栈轨迹如下", ste);
        } catch (Exception e) {
            logger.error("请求通信[" + reqURL + "]时偶遇异常,堆栈轨迹如下", e);
        } finally {
            httpClient.getConnectionManager().shutdown();
        }
        return responseContent;
    }

2、添加要请求的域名的证书到jdk

首先,从浏览器导出证书(一般火狐比较方便);

然后使用jdk自带的工具(/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/bin/keytool)导入证书(密钥库口令默认为changeit):

keytool -import -alias caiya.me -file /Users/caiya/Downloads/caiya.me.crt -keystore $JAVA_HOME/jre/lib/security/cacerts -trustcacerts -storepass changeit

可在导入证书前后执行命令:

keytool -list -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

显示如下:

密钥库类型: JKS
密钥库提供方: SUN
您的密钥库包含 92 个条目
digicertassuredidrootca, 2008-4-16, trustedCertEntry,
证书指纹 (SHA1): 05:63:B8:63:0D:62:D7:5A:BB:C8:AB:1E:4B:DF:B5:A8:99:B2:4D:43
trustcenterclass2caii, 2008-4-29, trustedCertEntry,
...
...

以对比证书密钥库的变化;

另外,删除证书命令如下:

keytool -delete -alias caiya.me -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

证书导入完成之后,再次请求caiya.me,有正常结果返回!

这时debug,于是找到了我添加的证书:

nginx + https配置和部署_第1张图片

To: Thu Feb 15 17:11:57 CST 2018]\n  Issuer: CN=CA 沃通免费SSL证书 G2, O=WoSign CA Limited, C=CN\n  SerialNumber: [    2f5f91a8 cd0e8e3f b0c806
45 = {HashMap$Entry@1600} "[\n[\n  Version: V3\n  Subject: OU=Equifax Secure Certificate Authority, O=Equifax, C=US\n  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5\n\n  Key:  Sun RSA public key, 1024 bits\n  modulus: 135786214035069526348186531221551781468391756233528066061569654028671100866720352830303278016129003918213826297308054231261658522889438712013757624116391437358730449661353175673177742307421061340003741057138887918110217006515773038453829253517076741780039735595086881329494037450587568122088113584549069375417\n  public exponent: 65537\n  Validity: [From: Sun Aug 23 00:41:51 CST 1998,\n               To: Thu Aug 23 00:41:51 CST 2018]\n  Issuer: OU=Equifax Secure Certificate Authority, O=Equifax, C=US\n  SerialNumber: [    35def4cf]\n\nCertificate Extensions: 7\n[1]: ObjectId: 1.2.840.113533.7.65.0 Criticality=false\nExtension unknown: DER encoded OCTET string =\n0000: 04 0D 30 0B 1B 05 56 33   2E 30 63 03 02 06 C0     ..0...V3.0c....\n\n\n[2]: ObjectId: 2.5.29.35 Criticality=false\nAuthorityKeyIdentifier


你可能感兴趣的:(nginx + https配置和部署)