SLB配置自己生成的SSL證書--雙向認證
因為公司業務的伺服器隻提供接口給移動端使用,不需要浏覽器通路,而且要求使用雙向認證,移動端要預埋證書,這就涉及到證書過期之後必須更新才能通路伺服器的問題。使用第三方機構的伺服器證書,有效期就1-2年,過期不得不更換,而移動端又不能確定使用者及時更新,安全性什麼的要求也高。總之,業務的需求決定了最終沒有購買第三方機構的伺服器證書,而是使用了openssl自己生成的證書。
之前是把證書直接配置在後端伺服器上,但考慮到後續業務的擴充以及高可用的要求,還是應該要做負載均衡,是以就嘗試在阿裡雲的SLB服務上做配置。
1.生成證書
首先生成證書,這裡我是寫了一個build_cert.sh腳本和配置來生成CA憑證,以及服務端和用戶端證書的,具體編寫可以參考這篇文章:https://blog.csdn.net/ustccw/article/details/76691248
我這裡生成測試證書使用的域名是:slb.test.mosiliang.top
接着在linux系統終端執行腳本build_cert.sh,就生成了ca.cert,ca.key,client.cert,client.key,server.cert,server.key等後續需要的檔案。
2.上傳證書
這些證書檔案生成之後還不能直接使用,需要做一些格式轉換。我這裡使用openssl到腳本所在路徑下執行如下指令:
openssl pkcs12 -export -in rsa/server.cert -inkey rsa/server.key -out server.pkcs12 -name server -CAfile rsa/ca.cert -caname root -chain
輸入密碼(随意寫,但要記住,因為後續還要用到,我這裡是:1523480205018),得到server.pkcs12伺服器證書。
但是SLB裡要求的證書格式是pem格式,是以要做PKCS#12 到 PEM 的轉換:
openssl pkcs12 -in server.pkcs12 -nokeys -out server.pem
接下來上傳證書到SLB做配置,先是上傳伺服器證書,這裡要選擇第三方簽發證書,如下圖:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0zZU5UeBRVTzx2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0kzM2UDMzcTM0IjMwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
接着下一步選擇檔案上傳,就是server.pem檔案,但是這裡生成的檔案直接上傳會提示格式錯誤,按照裡面的樣例格式說明(如下圖),
打開server.pem檔案發現這裡生成時多了一些額外的資訊,我們隻要把BEGIN CERTIFICATE和END CERTIFICATE的兩部分單獨拷貝到上傳證書的輸入框那裡就可以了。
接着是服務端私鑰的上傳,也就是前面生成的server.key檔案,這裡直接上傳也會提示格式不對,對比格式樣例,我這裡是收尾行少了個"RSA",即按照格式,隻要把BEGIN PRIVATE KEY改成BEGIN RSA PRIVATE KEY,END PRIVATE KEY改成END RSA PRIVATE KEY就可以了。
上傳伺服器證書之後就可以選擇證書了。另外,因為要實作雙向認證,是以還要啟用雙向認證并上傳CA根證書,如下圖:
這裡直接上傳ca.cert檔案就可以了。
3.測試驗證
驗證之前不要忘了在域名解析裡把前面生成證書的域名slb.test.mosiliang.top解析到自己SLB伺服器的VIP上。
上傳好證書,配置SLB的後端伺服器,建立虛拟伺服器組,并指定通路後端伺服器的端口,一般是80,我這裡是8080。伺服器組的選擇,看官網說明:
至于後端裡跑的測試服務環境,那就要自己去搭建了,我這裡之前就有測試的背景服務環境在,是以這裡隻要確定背景服務正常能夠通路到就行。
接着是用戶端的驗證,這裡我們的用戶端其實是移動端,但也可以先驗證一下浏覽器的通路。
浏覽器驗證
先http請求,驗證下後端伺服器通路情況是否正常:http://www.mosiliang.top:8080/xxxx
接着直接通過https通路浏覽器:https://slb.test.mosiliang.top/xxxx
這是會報錯的,因為我們開啟了雙向認證,要在電腦上安裝對應用戶端證書,之後才能通路。
用戶端證書就是使用前面生成的client.cert和client.key來生成,同樣是在腳本檔案所在路徑下執行指令:
openssl pkcs12 -export -in rsa/client.cert -inkey rsa/client.key -out client.p12 -name client -CAfile rsa/ca.cert -caname root -chain
輸入密碼(同樣是随意寫,這裡是:2563481407018),之後得到client.p12。
把client.p12安裝到win10上(安裝過程要輸入對應的密碼2563481407018),然後再打開浏覽器通路,大概因為是自己生成的證書(沒有第三方認證),浏覽器會彈框詢問,選擇對應證書确認就可以通路了,如下圖:
如果在SLB上關閉雙向認證,那麼這裡浏覽器不用安裝用戶端證書就可以通路。
移動端驗證
最後來驗證移動端,這裡使用的是Android端。
先生成用戶端信任的伺服器端證書,即把CA根證書導入,執行如下指令:
keytool -import -keystore client.truststore -keypass 1523480205018 -storepass 1523480205018 -alias ca -trustcacerts -file rsa/ca.cert
其中1523480205018是密碼,可以随意的寫,然後得到client.truststore檔案。
而Android端使用的一個證書格式要求是bks,是以這裡還要把client.truststore轉成bks格式的。這裡就要借助工具portecle.jar來轉換,工具下載下傳:https://sourceforge.net/projects/portecle/
大概步驟如下:
運作:java -jar portecle.jar
打開client.truststore檔案,如下圖:![]()
SLB配置自己生成的SSL證書--雙向認證
輸入前面設定的對應密碼,之後轉換格式,如下圖:![]()
SLB配置自己生成的SSL證書--雙向認證
再儲存檔案,如下圖:最終得到client_slb.bks檔案。![]()
SLB配置自己生成的SSL證書--雙向認證 ![]()
SLB配置自己生成的SSL證書--雙向認證
其中p12轉換bks時可能會報錯:
java.security.invalidKeyException:lllegal key size
原因是限制了密鑰長度,參考這裡:
https://blog.csdn.net/ainiyiwan123/article/details/796225
解決:
下載下傳JCE:https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
得到jce_policy-8.zip,解壓,把local_policy.jar和US_export_policy.jar複制到本地JDK對應安裝目錄下,我這裡是/usr/lib/jvm/jdk1.8.0_25/jre/lib/security,替換原來的檔案。
再重新開機portecle正常。
接下來,使用client_slb.bks和client…p12來做Android端驗證。
我們寫個demo來驗證,先把client_slb.bks和client…p12(重命名為client_slb.p12)放到assets目錄下;
接着是代碼處理,這裡連接配接用的是OkHttp,MainActivity.java的部分代碼如下:
public void btnParamTest(View view) throws Exception {
OkHttpClient mOkHttpClient = SSLHelper.getOkHttpClient(this);
Request request = new Request.Builder()
.url("https://slb.test.mosiliang.top/xxx")
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i("lyl","onFailure------222--------" + e.toString());
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.i("lyl","onResponse------222--------" + response.body().string());
}
});
}
SSLHelper裡的方法getOkHttpClient:
public static OkHttpClient getOkHttpClient(Context context) {
OkHttpClient client = null;
try {
// 伺服器端需要驗證的用戶端證書
KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
// 用戶端信任的伺服器端證書
KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);
InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
InputStream tsIn = context.getResources().getAssets().open(KEY_STORE_TRUST_PATH);
try {
keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
ksIn.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
tsIn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((trustStore));
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
//密鑰管理器
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[] { trustManager }, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
client = new OkHttpClient.Builder()
.connectTimeout(300*1000,TimeUnit.MILLISECONDS)
.readTimeout(300*1000,TimeUnit.MILLISECONDS)
.writeTimeout(300*1000,TimeUnit.MILLISECONDS)
.sslSocketFactory(sslSocketFactory, trustManager)
.hostnameVerifier(new UnSafeHostnameVerifier())
.build();
} catch (Exception e) {
e.printStackTrace();
}
return client;
}
相關配置參數:
private static final String KEY_STORE_TYPE_BKS = "bks";
private static final String KEY_STORE_TYPE_P12 = "PKCS12";
public static final String KEY_STORE_CLIENT_PATH = "client_slb.p12";//P12檔案
private static final String KEY_STORE_TRUST_PATH = "client_slb.bks";//truststore檔案
public static final String KEY_STORE_PASSWORD = "2563481407018";//P12檔案密碼
private static final String KEY_STORE_TRUST_PASSWORD = "1523480205018";//truststore檔案密碼
找個終端機器(如Android手機)來跑demo,運作通路,可以正常通路。
再驗證下雙向認證是否有效,把用戶端證書的配置注釋掉,如下圖,初始化sslContext時設定km為null。
這時候再請求通路就會報錯,傳回log資訊如下圖:
而把SLB伺服器上配置的”啟用雙向認證按鈕”關掉,再去請求,驗證能夠正常通路。說明這裡的雙向認證配置OK。
總結:在SLB負載均衡服務上可以配置openssl生成的證書,但一般伺服器證書都會購買第三方機構的,具體看使用場景吧。不過具體的配置差不多,不過是不用自己生成證書而已。
最後,分享一些阿裡雲服務活動:
雲産品通用紅包:https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=wbq4iya3
建站優惠:https://www.aliyun.com/jianzhan/?userCode=wbq4iya3
高性能雲伺服器特惠:https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=wbq4iya3
商标服務:https://tm.aliyun.com/?userCode=wbq4iya3
新使用者專享:https://promotion.aliyun.com/ntms/act/shoppingcart.html?userCode=wbq4iya3