跨域這個話題已經談了不少年了,怎麼如今又要談這個問題?原本是能夠沒必要再提了的,可是因爲Chrome 86
版本之後又增長了不少限制,致使咱們不得再也不次提起。html
對於前端開發來講,跨域一般有兩種方式,一種是在服務端修改nginx
配置,在response headers
裏添加CORS
設置,另外一種是在本地架設代理。咱們先談第一種。前端
本來在nginx
裏添加CORS
已是一種常規操做,簡單到無以復加:node
location /somewhere/ { if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin "$http_origin"; add_header Access-Control-Allow-Credentials "true"; add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; add_header Access-Control-Allow-Headers "sitessubid,Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since"; add_header Content-Length 0; add_header Content-Type text/plain; return 200; } if ($request_method = POST) { add_header Access-Control-Allow-Origin "$http_origin"; add_header Access-Control-Allow-Credentials "true"; } }
而後咱們只要在每一個axios
或者fetch
請求裏添加withCredentials
就能自動把對應該服務器的cookies
隨請求一併發送了。react
但問題在Chrome 86
版本之後,Chrome
強制給從服務端下發的每一個cookie
都增長了一個缺省的SameSite
屬性,而且強制把這個屬性設置爲Lax
,從而致使咱們的withCredentials
不起做用,只要是跨域的狀況,cookies
連取都取不到,更不要提發送了。在這裏我不想再詳述SameSite
的原理,感興趣的能夠去這裏瞭解。ios
這就須要咱們必須從服務端入手,修改nginx
下發cookies
時的SameSite
屬性。nginx
修改SameSite
又分爲兩種狀況:若是你用的是低版本的nginx
,可能還不必定能徹底解決問題,目前惟一能修改的變通之道是修改cookie
的path
,使它後面帶上SameSite
屬性,例如這樣:git
proxy_cookie_path ~(.*) "$1; SameSite=none";
這裏實際修改的是cookie
裏的path
值,但因爲附加了;
,因此瀏覽器會認爲自;
之後的是新屬性,因此也會接受這個SameSite
設定。axios
可是僅此還不夠,Chrome
又要求若是SameSite
值爲none
的話,則還必須設置secure
屬性,也就是要求全部下發cookie
的域名必須使用https
協議,因此最終修改的結果是:跨域
proxy_cookie_path ~(.*) "$1; SameSite=none; secure";
但這種狀況只能解決相似於cookie
是以path
結尾的狀況,若是上游服務器下發cookie
時不止有path
,而且在path
結尾指定了其它的SameSite
,那就無能爲力了,由於這麼修改path
以後cookie
會變成:瀏覽器
path=/; SameSite=none; secure; SameSite=Lax
瀏覽器仍然以最後一個SameSite=Lax
爲準。目前低版本nginx
對這個問題無解。
但若是你是nginx 1.19.3
及以上,新增了一個屬性,能夠更好地解決這個問題:
proxy_cookie_flags one samesite=none;
添加secure
標誌的方法相似,參考官方文檔,不贅述。
由上可知,咱們能夠經過修改nginx
的方法來改造跨域cookies
的發送。但這時若是你又不想跨域了,還想用本地代理的方式來解決,又會遇到一個問題:由於咱們上面在下發cookies
的時候,缺省地給每一個cookie
都帶上了secure
屬性,這就致使咱們在使用本地代理的時候反而沒法設置cookies
,由於咱們本地的開發服務器每每使用的是相似於http://localhost:8080
這樣的地址,它不是https
協議,因此沒法設置secure
的cookies
,那麼怎麼辦呢?
咱們只能動手改造咱們的本地服務器,使它也支持https
協議:
第一步,咱們先生成根證書:
openssl genrsa -des3 -out rootCA.key 2048 openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem
這時候咱們會獲得兩個文件,一個是rootCA.key
,一個是rootCA.pem
。
咱們把pem
文件導入系統的鑰匙鏈,並給予它徹底的信任,這樣之後再由它簽發的證書才能被系統承認,不然即便咱們用它簽發了證書,瀏覽器同樣會拒絕。
第二步,生成localhost
證書:
咱們先創建一個名爲server.csr.cnf
的文件:
[req] default_bits = 2048 prompt = no default_md = sha256 distinguished_name = dn [dn] C=US ST=RandomState L=RandomCity O=RandomOrganization OU=RandomOrganizationUnit emailAddress=hello@example.com CN = localhost
而後執行如下命令生成server.key
文件
openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key -config <( cat server.csr.cnf )
再建立一個v3.ext
文件:
authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment subjectAltName = @alt_names [alt_names] DNS.1 = localhost
而後執行如下命令生成server.crt
文件:
openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 500 -sha256 -extfile v3.ext
好了,到此爲止,這個server.key
和server.crt
文件就是咱們真正須要的兩個文件,咱們把它們引入系統,不一樣的系統有不一樣的引入方法,如下例子是react
的方法,創建一個.env
文件:
SSL_CRT_FILE=server.crt SSL_KEY_FILE=server.key HTTPS=true
這時候再啓動你的工程,localhost
就帶有https
協議了。
跨域就是這麼麻煩,但不論如何麻煩,咱們不辭麻煩,也必定要解決它!