記一篇http,ws通過nginx加殼打造https和wss

記一篇http,ws通過nginx加殼打造https和wss

產(chǎn)生背景

我們通常開發(fā)的應(yīng)用都是基于http的,但是在使用h5的notification功能的時候,要求必須是https才行,所以在這種類似的情況下不得不使用https,但是我們又不想改動原來的應(yīng)用。這個時候我們就需要通過nginx做反向代理,在nginx這層加個ssl的殼。相當(dāng)于是用戶訪問nginx時是https,nginx轉(zhuǎn)發(fā)到目的服務(wù)器的時候就已經(jīng)變成http了。我們的ssl的殼只是加在nginx這層。

ws加殼變成wss的原因是因為我們的應(yīng)用在web端使用了websocket(連接mqtt),但是因為原應(yīng)用變成https之后,ws會被瀏覽器broken,意思就是我們使用https,那么就必須使用wss。當(dāng)然wss也是使用nginx反向代理,加個殼。

好了背景了解了,我們現(xiàn)在來開始處理這個轉(zhuǎn)變過程中會出現(xiàn)的一些問題和解決辦法。

第一步http轉(zhuǎn)https

這一步,我們使用openssl可以生成自簽名的證書,證書是.pem或者cer都可以,這個不影響。

生成的時候會讓我們填一些信息,注意一下common_name,填這個信息的時候需要填成域名!后續(xù)會有其他方式生成,那個時候就不用填域名了,但是這里我們需要填成域名。

命令:

生成秘鑰: openssl genrsa -out privkey.pem 1024/2038
生成key: openssl req -new -x509 -key privkey.pem -out server.pem -days 365

生成之后在nginx端配置上相應(yīng)的證書,我為了方便,將證書放在和nginx.conf同樣的位置了,后續(xù)也一樣,我就不提了。

http {
...
 server {
        listen       443 ssl;
        server_name www.bb.com;
        
        ssl_certificate server.pem;
        ssl_certificate_key privkey.key;
        error_log   logs/error.log;
        client_max_body_size 60M;
        client_body_buffer_size 512k;
        location ~/.* {
            proxy_pass   http://127.0.0.1:7080;
        }
    }
...
}

配置完成之后,在host配置www.bb.com的本地DNS。

配置完之后,我們使用這個域名打開應(yīng)用頁面。這個時候會出現(xiàn)如下的界面,不安全的鏈接。當(dāng)然,在這種情況下我們可以直接點高級,繼續(xù)前往也能正確訪問。

not_auth_ssl.png

我們從圖中可以看到是ERR_AUTHORITY_INVALID錯誤,這種是認(rèn)證錯誤,說明證書不被信任。這種情況我們可以通過chrome導(dǎo)入受信任證書,或者通過windows的運行,輸入certmgr.msc指令,導(dǎo)入我們生成的server.pem證書。

導(dǎo)入過后,我們再次重啟瀏覽器,會發(fā)現(xiàn)依然是這個界面,只是錯誤變成了ERR_CERT_COMMON_NAME_INVALID,這個是錯誤的common_name,這個是啥東西,這個就是我們的域名不匹配證書導(dǎo)致的。

common_name_err.png

不管是上面的哪種錯誤,如果只是https,那么整個就算是完了,沒有任何其他的問題,這種錯誤可以不用管它,但是如果有wss在的話這種情況就就必須要處理了。

wss

我們先來試試不處理上訴問題時wss會怎樣。

首先我們必須明確的是,不管是ws還是wss,我們都得走nginx轉(zhuǎn)。那么我們先來配一下wss的nginx轉(zhuǎn)發(fā)的配置(和上面使用的是同一個nginx,只是加了個配置)。這里我們mqtt加了新的域名,im.mqtt.chat,并且我們使用8083為ssl端口。

http{
...
server {
        listen 8083 ssl;
        server_name im.mqtt.chat;
    
        ssl_certificate server.pem;
        ssl_certificate_key privkey.key;
        ssl_protocols SSLv3 SSLv2 TLSv1 TLSv1.1 TLSv1.2;
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        # ssl_prefer_server_ciphers on;
    

        location /mqtt{
            #反向代理到mqtt的ws端口8083,同時協(xié)議轉(zhuǎn)換為http,這樣服務(wù)器端代碼就不需要做修改
            proxy_pass http://192.168.55.111:8083;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            #由于服務(wù)器端源碼(建議大家做好大小寫匹配)只匹配了"Upgrade"字符串,所以如果這里填"upgrade"服務(wù)器端會將這條http請求當(dāng)成普通的請求,導(dǎo)致websocket握手失敗
            proxy_set_header Connection "Upgrade";
            proxy_set_header Remote_addr $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_read_timeout 600s;
        }
    }
...
}

從上面的配置中,我們可以看到,我們使用和https一樣的證書,我們在轉(zhuǎn)發(fā)的過程中把/mqtt的wss請求轉(zhuǎn)發(fā)到mqtt的地址,也就是proxy_pass的值,可以看到協(xié)議也是轉(zhuǎn)成http了,8083是192.168.55.111這臺機(jī)器上mqtt的ws監(jiān)聽端口。貼個圖吧。

common_name_err.png

這里簡單介紹一下為什么是ws會轉(zhuǎn)給http吧。

HTTP/1.1 Upgrade

詳細(xì)的請查詢:HTTP升級機(jī)制

http/1.1提供了一個升級的機(jī)制,協(xié)議的升級請求總是由端發(fā)起的;暫時沒有服務(wù)端請求協(xié)議更改的機(jī)制。當(dāng)客戶端試圖升級到一個新的協(xié)議時,可以先發(fā)送一個普通的請求(GET,POST等),不過這個請求需要進(jìn)行特殊配置以包含升級請求。

特別這個請求需要添加兩項額外的header:

# 設(shè)置Connection頭的值為“Upgrade”來指示這是一個升級請求
Connection: Upgrade
# Upgrade頭指定一項或多想?yún)f(xié)議名,按照有銜接排序,以逗號分隔
Upgrade: protocols

如果服務(wù)器決定升級這次連接,就會返回一個 101 Switching Protocols響應(yīng)狀態(tài)碼,和一個要切換到的協(xié)議的頭部字段Upgrade。 如果服務(wù)器沒有(或者不能)升級這次連接,它會忽略客戶端發(fā)送的 "Upgrade 頭部字段,返回一個常規(guī)的響應(yīng):例如一個200 OK).

服務(wù)在發(fā)送 101 狀態(tài)碼之后,就可以使用新的協(xié)議,并可以根據(jù)需要執(zhí)行任何其他協(xié)議指定的握手。實際上,一旦這次升級完成了,連接就變成了雙向管道。并且可以通過新協(xié)議完成啟動升級的請求。

所以,再來看我們的nginx配置當(dāng)中多出來的其中兩項:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";

測試

配置好之后,我們來看看頁面鏈接mqtt的情況。

mqtt_auth_err.png

我們打開控制臺,可以咋netwrk下的ws標(biāo)簽下看到ws的請求,在console頁可以看大ws證書出現(xiàn)的問題是ERR_CERT_AUTHORITY_INVALID,當(dāng)然也有可能是和之前https一樣的COMMON_NAME_ERR.這個就不演示了。反正這種錯誤就說明證書是不受信任的。

但是可能我們會想,我們不是已經(jīng)把證書導(dǎo)入了嗎,而且導(dǎo)入的還是受信任的根證書,為什么還會使不受信任呢。其實我們https連接的時候就可以看出來,路徑上https是被叉掉了的,這就說證書是不信任的,那是什么情況呢。

chrome解決自簽名證書無效

chrome驗證證書很嚴(yán)格,必須帶有Subject Alternative Name.

簽發(fā)csr(Certificate Signing Request 證書簽名請求文件)時,也就是我們生成證書的時候,我們需要修改openssl的配置。

linux下找一下openssl.cnf文件。cp一份到當(dāng)前open文件夾下面。

cp /etc/pki/tls/openssl.cnf ~/open/

第一步,在[ req ]節(jié)添加:

req_extetions = v3_req

第二步,添加v3_req節(jié)的配置

[ v3_req ] # Extensions to add to a certificate request 
basicConstraints = CA:FALSE 
keyUsage = nonRepudiation, digitalSignature, keyEncipherment 
subjectAltName = @alt_names

第三步,在alt_names添加受信任域名,這個受信任的域名用處就是,我們生成的證書只能是以下域名使用才行,否則會報COMMON_NAME_INVALID錯誤。

[ alt_names ]
134 DNS.1 = localhost
135 DNS.2 = im.mqtt.chat
136 DNS.3 = www.bb.com

改完之后是這樣:

openssl_cnf.png

貼下代碼:

[ req ]
...
req_extetions = v3_req
...
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = localhost
DNS.2 = im.mqtt.chat
DNS.3 = www.bb.com

為了我們生成一份兒證書,兩個域名都可以使用,我這里就直接把兩個域名都配置上了,方便而已,如果不嫌麻煩的可以分開。

配置好之后我們使用我們的修改好的配置文件來生成我們的證書,以下命令會一次性生成crt證書和key,生成的時候會讓填一些雜七雜八的信息,都可以亂填,因為我們配置這次我們修改了配置文件,證書信任的域名已經(jīng)配置了,所以在這步當(dāng)中讓我們填的common_name也可以不用像之前那樣填域名,隨便填個值都行。

openssl req -sha256 -newkey rsa:2048 -nodes -keyout  mssl.key -x509 -days 3650 -out  mssl.crt -config ./openssl.cnf -extensions v3_req

生成之后,我們也是需要將證書導(dǎo)入到受信任的證書。我這里就直接使用certmgr.msc來安裝了,當(dāng)然我們這次生成的是crt證書,這個可以直接雙擊安裝。我這里貼一個cermgr.msc的圖吧,在“操作->所有任務(wù)->導(dǎo)入”可以導(dǎo)入我們的證書。

certmgr.png

導(dǎo)入證書之后,修改一下nginx的https和wss的證書配置。

http {
  server {
        listen       443 ssl;
        server_name www.bb.com;
        
        ssl_certificate mssl.crt;
        ssl_certificate_key mssl.key;
        error_log   logs/error.log;
        client_max_body_size 60M;
        client_body_buffer_size 512k;
        location ~/.* {
            proxy_pass   http://127.0.0.1:7080;
        }
    }
    
    server {
        listen 8083 ssl;
        server_name im.mqtt.chat;
    
        ssl_certificate mssl.crt;
        ssl_certificate_key mssl.key;
        ssl_protocols SSLv3 SSLv2 TLSv1 TLSv1.1 TLSv1.2;
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        # ssl_prefer_server_ciphers on;
    

        location /mqtt{
             #反向代理到mqtt的ws端口8083,同時協(xié)議轉(zhuǎn)換為http,這樣服務(wù)器端代碼就不需要做修改
            proxy_pass http://192.168.55.111:8083;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            #由于服務(wù)器端源碼(建議大家做好大小寫匹配)只匹配了"Upgrade"字符串,所以如果這里填"upgrade"服務(wù)器端會將這條http請求當(dāng)成普通的請求,

導(dǎo)致websocket握手失敗
            proxy_set_header Connection "Upgrade";
            proxy_set_header Remote_addr $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_read_timeout 600s;
        }
    }   
}

可以看到我們兩個地方都是改成了mssl.crt和mssl.key。reload一下nginx(nginx -s reload).

可以慶祝了

重啟瀏覽器,我們可以看到,我們的鏈接變成安全的鏈接了。

mqtt也正常鏈接,沒有報錯了,查看mqtt的鏈接,也可以正常看到返回的是101,協(xié)議升級,header中Upgrade字段也返回了本次升級的協(xié)議是websocket。

測試通過,h5的Notification功能也正常使用。到此為止,ws和http加殼就算完成了。

result.png

over~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容