Tomcat單向Https驗證搭建,並親自實現與主流瀏覽器、Android/iOS用戶端安全通訊,androidios
眾所周知,iOS9已經開始在連網方面預設強制使用Https替換原來的Http請求了,雖然Http和Https各有各的優勢,但是總得來說,到了現在這個安全的資訊時代,開發人員已經離不開Https了。
網上有很多搭建Https的教程,但是比較零散,Web瀏覽器端和移動端具體部署也不是特別明確,如果真的用於項目中,還需要折騰一番,本人直接來個項目層級的Demo。
在開始之前,我總結一下keytool這個認證工具需要處理的幾種常見尾碼格式的意義:
jsk/keystore, 表示一個密鑰庫,裡面可以包含多個密鑰條目(認證),密鑰條目(認證)還可以分私人的和信任的等,私人的一般包括私密金鑰、公開金鑰和密鑰條目資訊,信任的一般包括公開金鑰和密鑰條目資訊(密鑰憑證)。開啟密鑰庫需要一個密碼,同時開啟每個私人密鑰條目也需要一個密碼(但一般建議將開啟私人密鑰條目的密碼設定跟開啟密鑰庫密碼相同,省的弄亂了,以下我的Demo示範是設定相同的),做過給安卓apk簽名打包的一定能體會到這個。
csr/certreq,認證請求檔案,你把這個提交給CA,CA會給你頒發cer格式的含有公開金鑰和密鑰條目資訊的認證(密鑰憑證)給你。
cer,用於儲存某個密鑰條目(認證)的公開金鑰檔案,一般你提交了csr給CA後,CA會頒發給你,你也可以通過自簽名的CA頒發,如果你已經有密鑰條目(認證)在密鑰庫裡,也可以從jsk/keystore中的某個密鑰條目(認證)匯出其公開金鑰和密鑰條目內容的認證(密鑰憑證)。
綜上,其實最簡單的理解就是密鑰庫就相當於SQL資料庫,各種密鑰條目(認證)就相當於SQL資料庫表 ,一個SQL資料庫表其實跟其它的表又有父子(外鍵)關係的,這種關係叫做密鑰條目(認證)的密鑰鏈。為了描述更加方便,以下將《密鑰庫》描述詞叫做《認證庫》,《密鑰條目》描述詞叫做《認證》,cer格式的公開金鑰和密鑰條目內容的認證叫做《密鑰憑證》。
接下來開始示範Demo樣本:
1、產生伺服器端認證庫和認證:(產生伺服器端認證庫和認證可以有多種方式,推薦通過走第三方CA方式,這樣產生的認證以後更具有保障性和安全性(尤其是對web用戶端,可以啟動“綠色地址欄/安全鎖 地址欄顯示單位名稱 EV國際認證標識”等等))
1-1-1、方式一、使用keytool,產生自簽名的CA認證和自簽名的server認證(下面產生的CA是自簽名的,當然下面產生的server也是自簽名的,這些認證在瀏覽器上使用絕對不會出現綠條):
1.產生自簽名CA:keytool -genkey -v -alias ca -keyalg RSA -keystore D:\ca_cert_lib.jks -validity 3650
2.產生伺服器憑證:keytool -genkey -v -alias server -keyalg RSA -keystore D:\server_cert_lib.jks -validity 365
注意認證名叫ca定義為自簽名的CA認證,認證名叫server定義為伺服器憑證,它們分別儲存在認證庫路徑為 D:\ca_cert_lib.jks 和 D:\server_cert_lib.jks 中
之所以要分自簽名的CA認證和server伺服器憑證,是因為正常情況下我們的server伺服器憑證是需要向第三方CA申請的,第三方CA會用它的根憑證給你產生一份公開金鑰認證(這個過程叫做第三方CA給你簽名),而此處就是要自導自演展示自簽名的CA給server認證簽名這個過程
1-1-2、用自簽名的CA給server簽上CA的簽名(server本身也是自簽名的,下面要做的相當於將server的自簽名換成CA的簽名,也許你會問CA的簽名是誰的,CA也可以是別人的,比如如果沃通願意給你的CA簽名的話,那麼CA的頒發者就是沃通,我這裡的Demo示範沒有權威機構給它簽名,所以我這個CA就是自己給自己簽名的,這個CA其實就是ROOT認證,只不過不會被任何用戶端信任(如:瀏覽器等)而已,即用我這個CA簽發的所有server伺服器憑證在任何瀏覽器上絕對不會出現綠條):
在給server簽名之前,查看一下當前認證庫情況,它們的確都是各自給自己簽名的:
keytool -list -v -keystore D:\ca_cert_lib.jks
keytool -list -v -keystore D:\server_cert_lib.jks
現在使用自簽名CA給server簽名(如果你要沃通CA給你server簽名,就把下面的csr交給沃通):
1.產生server的認證請求檔案:keytool -certreq -alias server -keystore D:\server_cert_lib.jks > D:\server.csr (linux上:keytool -certreq -alias server -keystore <路徑>/server_cert_lib.jks | tee <路徑>/server.csr)
2.使用自簽名的CA對server的認證請求檔案進行簽名頒發伺服器server.cer密鑰憑證:keytool -gencert -alias ca -keystore D:\ca_cert_lib.jks -infile D:\server.csr -outfile D:\server.cer
3.產生自簽名CA的公開金鑰檔案:keytool -export -alias ca -keystore D:\ca_cert_lib.jks -rfc -file D:\ca.cer
此時可以先查看以下ca.cer和server.cer密鑰憑證具體內容(注意ca.cer是自簽名CA的公開金鑰檔案,其頒發者還是它自己,而server.cer是server伺服器的公開金鑰檔案,其頒發者是自簽名的CA,兩者是有本質區別的,下面安裝回複後可以看到這個區別),不過其實他們都是個Base64過的字串:
keytool -printcert -rfc -file D:\ca.cer
keytool -printcert -rfc -file D:\server.cer
安裝認證回複(回複這個翻譯也許不太好,反正這個意思就是:將CA頒發的cer密鑰憑證安裝到server伺服器端認證庫,前提條件是CA的cer密鑰憑證也需要先被安裝):
1.先安裝CA的密鑰憑證(這步不可以少,否則下面的認證回複沒安裝):keytool -importcert -alias ca -keystore D:\server_cert_lib.jks -file D:\ca.cer
2.安裝server的密鑰憑證(安裝認證回複(被CA簽名過的)):keytool -importcert -alias server -keystore D:\server_cert_lib.jks -file D:\server.cer
此時再查看下伺服器server認證:keytool -list -v -keystore D:\server_cert_lib.jks -alias server
這時發現這個server認證變化挺大的,一是認證連變長了,變成2了,這個server認證附帶了上一級認證SELF CA ROOT CERT的資訊,其次是server的發行者變成了SELF CA ROOT CERT,這也就是說明成功的使用自簽名的CA給server簽名成功了
1-2-1、通過權威CA(第三方SSL認證機構)產生,如通過沃通產生免費/收費伺服器端密鑰庫和認證,CA產生的認證更具有保障性,最直觀的表現是用戶端用Web瀏覽器訪問該Https網站時會有綠色標識(當然要顯示越華麗就得給權威CA交更多的錢)如github:,以下示範使用沃通申請免費的DV SSL認證。
1-2-1、登陸沃通,申請一個免費的DV SSL認證。
1-2-2、申請需要先綁定網域名稱
1-2-3、申請完後需要驗證網域名稱,驗證網域名稱這個事就自己去搞定吧
1-2-4、上面用自簽名的CA給server認證簽名已經提到了如何產生csr檔案,此處通過提交認證申請檔案csr申請的步驟略。以下示範線上產生,即本次講的通過沃通CA自動產生密鑰憑證,順便把server伺服器憑證庫也一併產生好並將密鑰憑證匯入到這個認證庫,此處輸入的密碼實際上既是server伺服器憑證庫密碼也是server伺服器憑證(此種方式產生的認證名字叫做1,這個1對應上面自簽名CA匯入的server認證)的密碼
輸入密碼產生認證之後就可以下載沃通CA頒布給你server伺服器端用的認證庫和認證了,然後部署到對應的伺服器程式中,本案例部署到tomcat,為了保持統一性和直觀性此處將沃通CA頒發的認證庫名andy5.me.jks改名為server_cert_lib.jks
一般通過此時產生的認證名字(alias)叫做:1,對應自簽名CA方式中的server認證
因此你拿到了上面的沃通頒發的認證後,你還可以繼續頒發給別人,這些你頒發的認證都是可信任的,因為沃通上面的根憑證一定是可信任的,不然沃通本身就是不可以信任的。
2、通過上面的步驟,已經得到了含有CA頒發的認證的認證庫server_cert_lib.jks了,接下來,給伺服器程式tomcat配置認證庫(可以理解為給伺服器端安裝server密鑰庫)
2-1、在tomcat的安裝目錄/conf/server.xml中配置和啟用以下port為8443的Connector
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="D:\\server_cert_lib.jks" keystorePass="s123456"
/>
3、接下來,要操作用戶端了(這裡的用戶端包括多種:1是主流瀏覽器、2是Android、3是iOS)
3-1、先從jks格式的伺服器端認證庫server_cert_lib.jks匯出cer格式的伺服器端密鑰憑證server.cer。
keytool -keystore D:\server_cert_lib.jks -export -alias server -file D:\server.cer
3-2、將server.cer伺服器端密鑰憑證匯入到用戶端信任認證庫(這個實現不能用keytool匯入了,而是要根據具體的各個用戶端平台進行實現,此步驟可以理解為給用戶端安裝伺服器端密鑰憑證)
3-2-1、Web瀏覽器實現:使用瀏覽器安裝伺服器端server.cer密鑰憑證
3-2-1-1、IE瀏覽器,直接雙擊server.cer,然後在憑證存放區——將所有認證放入下列儲存,選擇可信任的根憑證授權單位
3-2-1-2、Firefox瀏覽器,Firefox沒辦法直接將來自自簽名CA頒發的server.cer密鑰憑證匯入到認證機構中,因為Firefox會檢查你的伺服器密鑰憑證server.cer的最高一級頒發者是不是權威機構(權威第三方CA),不是的話,不會通過的,比如你在Firefox選項——進階——認證——查看認證——伺服器/認證機構:匯入server.cer,會提示無法匯入,因此解決方案是在不用匯入server.cer,而是在伺服器選項卡中添加列外地址,如果來自權威CA頒發的server.cer,則可以直接匯入。
3-2-2、Android用戶端實現
3-2-2-1、將伺服器端密鑰憑證server.cer放入安卓assert目錄,然後使用HttpsUtil.getSslSocketFactory進行初始化(該工具類的方法具體實現見後面的Demo代碼)
private void initHttpsEngine(boolean isSelfCa) { try { // 初始化伺服器端密鑰憑證,得到SSLSocketFactory SSLSocketFactory sslSocketFactory = HttpsUtil.getSslSocketFactory(new InputStream[]{getAssets().open ("server.cer")}); OkHttpClient.Builder builder = new OkHttpClient.Builder(); if (isSelfCa) { /** * 注意;如果你的server.cer是來自自簽名CA頒發的,那麼就要設定下面的customVerifier,主要是為瞭解決報以下異常, * 即跳過Hostname www.andy5.me在CA上的驗證,如果你的server.cer是來自第三方SSL權威機構頒發的,不用設定這個customVerifier * * javax.net.ssl.SSLPeerUnverifiedException: Hostname www.andy5.me not verified: * certificate: sha1/EnrjjhNxjvuDkO/rJqPmJ9XaIMs= * DN: CN=Andy Wu(www.andy5.me),OU=Andy5 Server,O=www.andy5.me,L=Guangzhou,ST=Guangdong,C=CN * subjectAltNames: [] */ HostnameVerifier customVerifier = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { // 指定SERVER_URL一定可以通過 if (SERVER_URL.equalsIgnoreCase(hostname)) { return true; } else { // 使用預設的OkHostnameVerifier進行驗證 return OkHostnameVerifier.INSTANCE.verify(hostname, session); } } }; mOkHttpClient = builder.sslSocketFactory(sslSocketFactory).connectTimeout(30, TimeUnit.SECONDS) .hostnameVerifier(customVerifier).build(); } else { mOkHttpClient = builder.sslSocketFactory(sslSocketFactory).connectTimeout(30, TimeUnit.SECONDS).build(); } } catch (IOException e) { e.printStackTrace(); } }
3-2-2-2、可以在任何地方對該伺服器發起Https請求了,如果是自簽名CA簽發的伺服器端server認證,需要忽略網域名稱驗證才能正常通訊(具體看Demo代碼),顯然也是不安全的。
public void testHttps(View v) { if (mOkHttpClient == null) { Toast.makeText(getApplicationContext(), "請先初始化用戶端密鑰庫和伺服器端公開金鑰!", Toast.LENGTH_SHORT).show(); return; } mTvResult.setText("正在從 " + HTTPS_SERVER_URL + " 擷取資料...."); mWvResult.loadData("", "text/html", "UTF-8"); Request request = new Request.Builder().url(HTTPS_SERVER_URL).build(); Call call = mOkHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, final IOException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { mTvResult.setText("從 " + HTTPS_SERVER_URL + " 擷取資料失敗!\n" + e); } }); } @Override public void onResponse(Call call, Response response) throws IOException { final String html = response.body().string(); runOnUiThread(new Runnable() { @Override public void run() { mTvResult.setText("從 " + HTTPS_SERVER_URL + " 擷取資料成功!"); mWvResult.loadData(html, "text/html", "UTF-8"); } }); } }); }
3-2-2-3、成功請求介面如下,軟體環境:MIUI 6(Android 4.4.2) + AS 1.5.1
3-2-3、iOS用戶端實現
3-2-3-1、將伺服器端密鑰憑證server.cer放入根目錄,然後使用HttpsUtil.configSecurityPolicy配置AFNetworking安全選項(該工具類的方法具體實現見後面的Demo代碼)
- (IBAction)testHttps:(UIButton *)sender { AFHTTPSessionManager *manager =[AFHTTPSessionManager manager]; NSArray *serverCersNames = [[NSArray alloc] initWithObjects:@"server.cer", nil]; [HttpsUtil configSecurityPolicy:manager.securityPolicy serverCers:serverCersNames]; manager.requestSerializer.timeoutInterval = 30.0f; manager.responseSerializer = [AFHTTPResponseSerializer serializer]; [_tvResult setText:[NSString stringWithFormat:@"正在從%@擷取資料....",HTTPS_SERVER_URL]]; [_wvResult loadHTMLString:@"" baseURL:nil]; [manager GET:HTTPS_SERVER_URL parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) { // } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSString *result = [[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding]; NSLog(@"擷取資料成功\n%@",result); [_tvResult setText:[NSString stringWithFormat:@"從%@擷取資料成功!",HTTPS_SERVER_URL]]; [_wvResult loadHTMLString:result baseURL:nil]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"擷取資料失敗\n%@",error); [_tvResult setText:[NSString stringWithFormat:@"從%@擷取資料失敗!\n%@",HTTPS_SERVER_URL,error]]; }];}
3-2-3-2、成功請求介面如下,軟體環境:iOS 9.2.1 + Xcode 7.2.1
4、此時,用戶端(web瀏覽器、Android、iOS)就可以向伺服器端發起Https請求了,詳細效果見Demo(為了相容示範自簽名CA,Demo使用的是自簽名伺服器憑證)。
5、額外補充說明:
1、關於自己內網測試問題,我所有使用的www.andy5.me這個網域名稱是可以改成IP的,一樣可以測試成功,當然我測試的時候是用nat123做了映射,主要是為了更接近真實環境。
參考:
http://blog.csdn.net/lmj623565791/article/details/48129405
http://callistaenterprise.se/blogg/teknik/2011/11/24/creating-self-signed-certificates-for-use-on-android
單向Https驗證Demo相關檔案.7z
Https雙向認證隨筆稍後發布
原創隨筆,轉載註明出處。