記一篇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ù)前往也能正確訪問竞膳。
我們從圖中可以看到是ERR_AUTHORITY_INVALID錯誤,這種是認證錯誤诫硕,說明證書不被信任坦辟。這種情況我們可以通過chrome導(dǎo)入受信任證書,或者通過windows的運行痘括,輸入certmgr.msc指令长窄,導(dǎo)入我們生成的server.pem證書滔吠。
導(dǎo)入過后纲菌,我們再次重啟瀏覽器,會發(fā)現(xiàn)依然是這個界面疮绷,只是錯誤變成了ERR_CERT_COMMON_NAME_INVALID翰舌,這個是錯誤的common_name,這個是啥東西冬骚,這個就是我們的域名不匹配證書導(dǎo)致的椅贱。
不管是上面的哪種錯誤,如果只是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這臺機器上mqtt的ws監(jiān)聽端口金矛。貼個圖吧芯急。
這里簡單介紹一下為什么是ws會轉(zhuǎn)給http吧。
HTTP/1.1 Upgrade
詳細的請查詢:HTTP升級機制
http/1.1提供了一個升級的機制驶俊,協(xié)議的升級請求總是由端發(fā)起的娶耍;暫時沒有服務(wù)端請求協(xié)議更改的機制。當(dāng)客戶端試圖升級到一個新的協(xié)議時饼酿,可以先發(fā)送一個普通的請求(GET榕酒,POST等),不過這個請求需要進行特殊配置以包含升級請求故俐。
特別這個請求需要添加兩項額外的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的情況股淡。
我們打開控制臺,可以咋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驗證證書很嚴格蚀瘸,必須帶有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
改完之后是這樣:
貼下代碼:
[ 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)入我們的證書砾层。
導(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加殼就算完成了智厌。
over~~~