SSL證書與Https應用部署小結(轉發)
為了提高網站的安全性,一般會在比較敏感的部分頁面採用https傳輸,比如註冊、登入、控制檯等。像Gmail、網銀等全部採用https傳輸。
https/ssl 主要起到兩個作用:網站認證、內容加密傳輸和資料一致性。經CA簽發的證書才起到認證可信的作用,所有有效證書均可以起到加密傳輸的作用。
瀏覽器與SSL證書
SSL最主要應用是在瀏覽器和Web伺服器之間,儘管不限於此。當然,安全本身是重要的內在屬性。但在表面上看,部署SSL 就是為了讓使用者瀏覽器裡看起來更安全一些,以增加使用者的信任感。所以很多企業更把它當作門面,而簽發機構也為此賣高價,尤其是國內的價格明顯高於國外的。
實際上SSL證書也可以做客戶端認證,使用者擁有自己特有的證書,用它可以證明自己的身份,當然也就用不著使用者名稱和密碼了。但這種用的很少,一般web伺服器也不支援。
內容加密傳輸更安全,如果只是為了加密,使用自簽發的證書也可以,但瀏覽器無法驗證證書,所以會給出一個非常嚇人的警告,所以自簽發證書不適合給外人使用,只適合內部使用,把這個證書 加入到自己的信任列表或忽略證書驗證即可,以後就不會繼續攔截了。
證書需要被少數一級或二級 CA 認證才有效。電腦保安中的信任就是一個信任鏈的關係,信任鏈最頂端的被稱為根證書。
自簽發的證書在技術上是完全一樣的,僅用於加密傳輸是沒問題的。但是不能被外人信任,所以一般僅用於內部使用。除了自簽發不被信任,如果證書過期、已被吊銷或者非證書所代表的域名也都是不被信任的,導致證書驗證出錯。
用於網站的證書需要被大眾信任,所以不能自簽發的證書,那就申請(購買)一個吧。
申請證書
1.證書類別
按證書包含域名數量分為:
- 單域名:只針對這個域名有效,不能用在其它域名下。
- 多域名:只針對列出的多個域名有效。
- 萬用字元域名(wildcard):對任意子域名有小,顯示的是 *.example.com。
注意:
SSL所說的單個域名是一個完整的域名,一個子域名就算一個,而非一個頂級域名。 如果網站有很多子域名,只需要申請真正需要的域名證書。
按驗證的類別分:
- 域名認證(Domain Validation):認證你的域名所有權和網站,申請驗證簡單,幾分鐘即可。
- 組織機構認證(Organization Validation):認證的域名和公司資訊,需要提交公司資料認證。
- 擴充套件認證(Extended Validation,簡稱EV):這種證書會在瀏覽器中出現“很明顯”的綠色位址列,給使用者的可信度最高。有安全評估保證。
說明:
個人或小站點可用一類或二類,企業一般用二類認證,少數企業會用到EV認證。
SSL證書需要向國際公認的證書證書認證機構(簡稱CA,Certificate Authority)申請。
CA機構頒發的證書有3種類型:
- 域名型SSL證書(DV SSL):信任等級普通,只需驗證網站的真實性便可頒發證書保護網站;
- 企業型SSL證書(OV SSL):信任等級強,須要驗證企業的身份,稽核嚴格,安全性更高;
- 增強型SSL證書(EV SSL):信任等級最高,一般用於銀行證券等金融機構,稽核嚴格,安全性最高,同時可以啟用綠色網址欄。
2. 證書價格
看了看網上SSL證書的價格,便宜的一般都是10美元左右一個子域名/每年,按不同類別、不同品牌等價格在幾十美元到幾百美元一年。比如能顯示綠色位址列的EV證書和萬用字元證書貴一些。國內自己的或代理的,比國外貴不少,動輒幾千元。其實就是由可信源認證了一下,類似於辦證,用起來沒什麼差別,並非越貴越好。
3. 簽發機構(“賣家”)
國外常見的SSL提供商有:Thawte,Go Daddy,VeriSign,RapidSSL,GeoTrust(QuickSSL),StartSSL,Comodo。
StartSSL、Go Daddy的比較便宜,GeoTrust、Comodo的價格適中,Thawte和VeriSign的價格較貴。
- Let’s Encrypt:免費,快捷,支援多域名(不是萬用字元),三條命令即時簽署+匯出證書。缺點是暫時只有三個月有效期,到期需續簽。
- StartSSL免費DV證書:免費,有效期是一年,比Let’s Encrypt長。
- Comodo PositiveSSL:便宜,單年9美刀,如果籤三年大概每年4至5美刀。可簽署ECC SSL證書。
- RapidSSL:單年簽署價格同PositiveSSL,並沒有什麼優缺點。
- 沃通(Wosign)免費DV證書:免費,簽發快,介面和官方資料都是中文。問題是上次CNNIC的二級CA冒籤Gmail的證書引起爭議,在國內有些人預防性拉黑所有國內CA的證書,包括沃通的。他們瀏覽你的站點時會被攔截和警告。
4. 免費的StartSSL
免費的是StartSSL,其它的一般只提供30免費試用。
但 StartSSL 提供的免費證書是一類的、僅對域名和email進行驗證,不對組織做驗證(也就是面向自然人的,非面向組織機構的),不過
僅作為域名驗證和資料加密也夠了,並且瀏覽器也認它,一般人也不會去看你的證書級別。適合個人和初創網站使用,以後有錢了再申請個收費的替換即可。
startsll網站改版後,申請過程簡單了,免費申請StartSSL證書,過程如下:
1、使用One Time Password Login登入
2、輸入郵箱和郵箱驗證碼,登入成功
3、選擇“Certificates Wizard”根據嚮導,選擇“Web Server SSL/TLS Certificate”進行證書的生成
4、填寫證書詳細資訊,生成證書
5、提交,生成證書,並下載證書即可。
最後,證書籤發給你後,最主要是保護好私鑰證書,這個丟失或洩漏就完了。因為如果被別人利用也就毫無安全性了,需要向證書籤發機構申請撤銷證書並申請新的證書,這當然也是要收費的。
應用規劃、配置和調整
並不是說有了SSL證書就沒事了,還要考慮應用中的使用問題,需要規劃、伺服器配置、應用調整等多個環節。
SSL比 http 要消耗更多cpu資源(主要是在建立連線的階段,之後還要對內容加密),所以對一般網站,只需要對部分地方採用https,大部分開放內容是沒必要的,具體取決於你的業務要求。比如對於很多安全要求較低的網站,完全不用https也是可接受的。
某些頁面是同時支援 http 和 https ,還是隻支援 https、強制 https?
同時支援就是使用者用什麼協議訪問都可以,那麼使用者的請求主要就是由頁面本身的連結引導來的,因為一般使用者不會自己特意去修改位址列的。
一般我們的網站可以做成同時支援http和https,都可以訪問。但是這就容易有後面說的混合內容或混合指令碼的問題。
還可以規劃為部分頁面支援 https,一般公開頁面不用https,只是將部分地方的連結改為 https 就可以了。專門期望以 https 訪問的頁面中,引用的絕對URL可以明確的使用 https連結。
是否強制 https ?對於安全性高的網站或網站中的部分頁面,可以強制使用https訪問, 即使使用者在位址列裡手工把 https 改為 http, 也會被自動重定向回 https 上。比如可以通過配置web伺服器 rewrite 規則將這些 http url 自動重定向到對應的 https url 上(這樣維護比較簡單),而不用改應用。
解決混合內容問題(http和https)
混合內容是指:在https的頁面中混合了非https的資源請求,比如圖片、css、js 等等。如果是混合了非 https 的 js 程式碼,則被稱為混合指令碼。
混合內容的危害:如果只是混合了不安全的圖片和css,那麼受中間人攻擊篡改,一般只會影響頁面的顯示,危害相對小一點。如果是混合了不安全的 js 程式碼,則這個不安全的 js 可以完全訪問和修改頁面中的任何內容,這是非常危險的。
另請參看,Chrome對混合指令碼危害的說明與提示:Trying to end mixed scripting vulnerabilities
所以,只有頁面本身和所有引用的資源都是 https 的瀏覽器才認為是安全的,只要其中引用了非安全資源(即使圖片),瀏覽器都會給出不安全的提示,特別是有 js 的情況。如果瀏覽器提示不安全,那樣我們就達不到原來目的了。我們費了半天功夫去申請 SSL 證書,配置Web伺服器,最後如果因為混合內容而前功盡棄就太糟了。咱繼續努力吧,想辦法讓所有引用資源都是安全的。
理論上,混合了第三方的內容,即使是SSL的第三方內容也不是很好。因為使用者信任的是你,而不是第三方,即使第三方也支援https,但你能保證第三方就絕對安全嗎。不引用任何第三方才是絕對安全的,但這樣太嚴格了,安全其實也是一個 tradeoff 的問題,需要考慮很多方面的平衡。還好,起碼現在瀏覽器認為已經是安全的了。
引用第三方檔案的問題(如 CDN 分發的檔案)
簡單地說,這個問題要麼有第三方提供 https 支援,要麼不用它(用自己本地的)。
一般我們會引用由 CDN 分發的檔案,比如某個 js 庫檔案,而不用訪問自己網站上的,這樣藉助 CDN 網路可以加快速度,這當然很好。
但是,如果我們在頁面中使用絕對 URL 直接引用這個檔案就無法自動使用 https 了!出現了混合協議內容,瀏覽器又該“變臉”了。
當SSL 遇上CDN 或 其它第三方檔案就有點麻煩,因為很多CDN還不支援SSL。如果支援 https 的話就可以直接用 https 的絕對URL了,即使是同時支援http 和 https 的頁面,這樣做也不算太浪費,起碼解決了問題。
因為CDN的雲檔案提供者,一般為每個cdn使用者建立一個單獨的子域名來使用,這樣的話,CDN提供者要想支援 https 就必須支援所有可能的子域名,因此要求CDN提供方使用那種萬用字元子域名的證書。
相對 URL、絕對 URL 與 只缺協議的URL(Protocol Relative URL)
相對url比較簡單,自動匹配使用者請求的 http 或 https 協議。
但是絕對 url 則不成,因為絕對 url 已經明確地寫上了協議: http://www.example.com/jquery.js 。
這個問題還有一個辦法解決,你一定沒見過這種形式: //www.example.com/jquery.js
哈哈,一個缺少協議的URL(實際上還算是相對URL),這種形式可以在瀏覽器中被正確補充上合適的協議!很多人都用這種方法。
但是,這裡有點小問題,IE7 和 IE8 處理這種缺少協議的URL的css 檔案時,同一個css檔案會下載兩次,詳見Steve的文章 。
JS 自動判斷當前協議
現在我們經常用 js 來載入其它 js 檔案或 其它別的檔案,如果是請求是相對URL則沒問題,如果是絕對URL怎麼辦?
其實 js 指令碼可以這樣:document.location.protocol 等於 ‘http:’ 還是 ‘https:’ 來判斷。例如在 Google Analytics 的嵌入程式碼中:
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
應用程式中如何判斷訪問協議
對於動態頁面,如 jsp、php等,也是可以動態判斷當前是否使用了 https 協議的。所以應用可以根據動態判斷,來生成不同的引用 URL。這樣雖然有點麻煩,但也算是解決了自動識別協議的問題,當然相對路徑總是不需要處理的。
比如在 jsp 中:
- request.isSecure() 為true 表示當前為 https ,false表示 http 訪問
- request.getScheme() 返回字串 https 或 http
注意,如果 tomcat 部署在其它web伺服器代理的後面,需要正確配置好才能返回正確結果,見本文最後一部分。
同源策略的問題
最後提醒一點:http 和 https是不同源的!即使後面的內容都一樣。所以 ajax 發請求的時候要使用正確協議的絕對URL才行。
相對URL的 ajax 請求沒關係。
Nginx 配置
小結一下 Nginx 配置SSL注意的問題,詳細安裝配置內容請參考其它資料,如官方 SSL模組 和 https配置文件。
首先檢查一下是否已安裝了 SSL模組,因為預設是不包含的。
用 nginx -V 命令檢查一下。如果沒有ssl模組則需要重新安裝(建議升級到最新版本),注意安裝時加上ssl 選項:
./configure --with-http_ssl_module
另外,nginx需要依賴 openssl 提供ssl支援,這個也要有。
nginx.conf 中的典型配置示例
listen 80;
listen 443 ssl;
ssl_certificate cert.pem; #修改具體檔案
ssl_certificate_key ssl.key; #修改具體檔案
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_protocols SSLv2 SSLv3 TLSv1;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;
上面第2-4項是關鍵。這些配置放在 server 塊就可以對其中的所有 location 生效了,並且同時支援 http 和 https 。或者把 http 和 https 分開配置也很常見。
合併證書配置檔案
和Apache配置不同,Nginx需要將伺服器證書和ca證書鏈合併到一個檔案中,作為 ssl_certificate 配置的內容。
例如,按照證書鏈從下向上的順序,我有三個證書:
- ssl.crt(自己域名的伺服器證書)
- sub.class1.server.ca.pem(startssl 的一類證書)
- ca.pem(startssl 的根證書)
把它們的內容按順序連線到的一個檔案中,每個內容另起一行,中間沒有空行或空格。
避免啟動時輸入密碼
配好之後,啟動nginx 要你輸入金鑰的密碼。這是因為 ssl_certificate_key
配置對應的檔案(也就是 startssl 給你的私鑰檔案)內容是加密的,需要輸入你建立這個時設定的密碼才能解密。這樣私鑰雖然很安全,但是每次重啟服務都要輸入一次密碼也太麻煩了。其實,只要證書改為解密了的內容,就可以避免每次輸入密碼。用如下命令即可:
openssl rsa -in ssl.key -out newssl.key
輸入密碼,就生成了解密後的私鑰內容,使用這個就OK了。
但是就像前面說的,一定要在伺服器上保護好它,例如:
chmod 400 ssl.key (僅root可讀)
優化SSL配置
SSL 很消耗 CPU 資源,尤其是在建立連線的握手階段。一是通過開啟 keepalive 可以重用連線。二是可以重用和共享ssl session,見上面ssl_session相關配置。
獨立Tomcat+SSL
Tomcat 是很常見的 Java應用伺服器,當然也可以作為獨立的 Web伺服器,所有使用者請求直接訪問 tomcat。
如果 Tomcat 作為獨立的Web伺服器,那麼就需要配置Tomcat就可以了,文件參考這裡 和 這個。主要是配置存放證書的 Keystore 和 聯結器Connector。
Java的keystore
keystore 是 Java 中專用並內建的一個類似於 openssl 的工具,一個 keystore 檔案就是一個“保險箱”(database),專門存放證書和金鑰,和相關的管理功能:生成自簽發的證書、金鑰、匯入匯出等。可以通過 keytool 命令或 Java api 互動。
利用keytool 命令將你的證書匯入進去。
Tomcat中Connector
tomcat中有三種 Connector 實現:block、nio 和 APR。前兩者使用Java SSL(這需要 keystore 的配置 ),APR使用OpenSSL(不需要用keystore,直接指定證書),配置略有不同。
Nginx+Tomcat+SSL
實際上,大規模的網站都有很多臺Web伺服器和應用伺服器組成,使用者的請求可能是經由 Varnish、HAProxy、Nginx之後才到應用伺服器,中間有好幾層。而中小規模的典型部署常見的是 Nginx+Tomcat 這種兩層配置,而Tomcat 會多於一臺,Nginx 作為靜態檔案處理和負載均衡。
如果Nginx作為前端代理的話,則Tomcat根本不需要自己處理 https,全是Nginx處理的。使用者首先和Nginx建立連線,完成SSL握手,而後Nginx 作為代理以 http 協議將請求轉給 tomcat 處理,Nginx再把 tomcat 的輸出通過SSL 加密發回給使用者,這中間是透明的,Tomcat只是在處理 http 請求而已。因此,這種情況下不需要配置 Tomcat 的SSL,只需要配置 Nginx 的SSL 和 Proxy。
在代理模式下,Tomcat 如何識別使用者的直接請求(URL、IP、https還是http )?
在透明代理下,如果不做任何配置Tomcat 認為所有的請求都是 Nginx 發出來的,這樣會導致如下的錯誤結果:
request.getScheme() //總是 http,而不是實際的http或https
request.isSecure() //總是false(因為總是http)
request.getRemoteAddr() //總是 nginx 請求的 IP,而不是使用者的IP
request.getRequestURL() //總是 nginx 請求的URL 而不是使用者實際請求的 URL
response.sendRedirect( 相對url ) //總是重定向到 http 上 (因為認為當前是 http 請求)
如果程式中把這些當實際使用者請求做處理就有問題了。解決方法很簡單,只需要分別配置一下 Nginx 和 Tomcat 就好了,而不用改程式。
配置 Nginx 的轉發選項:
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
配置Tomcat server.xml 的 Engine 模組下配置一個 Value:
<Valve className="org.apache.catalina.valves.RemoteIpValve" remoteIpHeader="X-Forwarded-For" protocolHeader="X-Forwarded-Proto" protocolHeaderHttpsValue="https"/>
配置雙方的 X-Forwarded-Proto 就是為了正確地識別實際使用者發出的協議是 http 還是 https。X-Forwarded-For 是為了獲得實際使用者的 IP。
這樣以上5項測試就都變為正確的結果了,就像使用者在直接訪問 Tomcat 一樣。
參考: