Nginx實戰(九)跨域配置(解決CORS報錯)

本章導讀

  • 這章開始寫寫一些小技巧,或者叫小功能
  • 目前很流行前後端分離
  • 即使不分離,如果需要用ajax調用,但是調用服務不在同域
  • 這個時候,nginx都可以幫上忙

開始前先看一個chrome的報錯:

:8080/#/auth/login:1 Access to XMLHttpRequest at ‘http://localhost:8830/extinterface/loginExt’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

再看看不修改前端代碼(比如js),也不修改java代碼(假設後端是java實現的),只修改下nginx的配置:

http {
	include       mime.types;
	add_header 'Access-Control-Allow-Origin' '*';
	add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
	add_header 'Access-Control-Allow-Credentials' 'true';
	default_type  application/octet-stream;
	
	map $http_origin $corsHost {
		default 0;
		"~http://130.51.23.17:8080" http://130.51.23.17:8080;
	}
}

本章要點

  • 首先要了解跨域,不同的ip,或者不同的域名,當然算跨域
  • 如果同樣的ip,不同的端口,算跨域嗎?當然算的
  • 解決的方法有很多,nginx只是其中一種
  • nginx解決,也有兩種配置方法,一種是反向代理,把不同域的a域名反向代理成同域的b域名
  • 另一種是設置header,使得服務端允許跨域
  • 這裏說的服務端允許跨域,不是說修改服務端的代碼(當然也可以這麼說,但是修改代碼要重新編譯發佈,太麻煩),而是通過nginx來訪問服務端,而中間的nginx設置了一下header
  • 如果是允許了跨域,可能會導致一些安全的問題,所以最好服務端應該在內網,並且有防火牆保護的

瞭解跨域以及產生原因

跨域是指a頁面想獲取b頁面資源,如果a、b頁面的協議、域名、端口、子域名不同,或是a頁面爲ip地址,b頁面爲域名地址,所進行的訪問行動都是跨域的,而瀏覽器爲了安全問題一般都限制了跨域訪問,也就是不允許跨域請求資源。

注意:跨域限制訪問,其實是瀏覽器的限制。理解這一點很重要。所以,當用java(或者其他語言)調用RESTful api,從來不會報什麼跨域錯誤。

跨域情況如下:

url 說明 是否跨域
http://www.cnblogs.com/a.js,http://www.a.com/b.js 不同域名
http://www.a.com/lab/a.jshttp://www.a.com/script/b.js 同一域名下不同文件夾
http://www.a.com:8000/a.jshttp://www.a.com/b.js 同一域名,不同端口
http://www.a.com/a.jshttps://www.a.com/b.js 同一域名,不同協議
http://www.a.com/a.jshttp://70.32.92.74/b.js 域名和域名對應ip
http://www.a.com/a.jshttp://script.a.com/b.js 主域相同,子域不同 是(cookie不可訪問)
http://www.a.com/a.jshttp://a.com/b.js 同一域名,不同二級域名(同上)

跨域的常見解決方法

目前來講沒有不依靠服務器端來解決跨域請求資源的技術,也就是說,如果服務端不允許跨域請求,那麼單靠客戶端無論怎麼弄,都是不行的。

  • 1.jsonp 需要目標服務器配合一個callback函數。

  • 2.window.name+iframe 需要目標服務器響應window.name

  • 3.window.location.hash+iframe 同樣需要目標服務器作處理。

  • 4.html5的 postMessage+ifrme 這個也是需要目標服務器或者說是目標頁面寫一個postMessage,主要側重於前端通訊。

  • 5.CORS 需要服務器設置header :Access-Control-Allow-Origin。

  • 6.nginx反向代理 這個方法一般很少有人提及,但是他可以不用目標服務器配合,不過需要你搭建一箇中轉nginx服務器,用於轉發請求。

方法一:add_header

當出現403跨域錯誤的時候 No ‘Access-Control-Allow-Origin’ header is present on the requested resource,需要給Nginx服務器配置響應的header參數

解決方案

只需要在Nginx的配置文件中配置以下參數:

location / {  
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

    if ($request_method = 'OPTIONS') {
        return 204;
    }
} 

上面配置代碼即可解決問題了,不想深入研究的,看到這裏就可以.

解釋

1. Access-Control-Allow-Origin

服務器默認是不被允許跨域的。給Nginx服務器配置Access-Control-Allow-Origin *後,表示服務器可以接受所有的請求源(Origin),即接受所有跨域的請求。

2. Access-Control-Allow-Headers

是爲了防止出現以下錯誤:

Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

這個錯誤表示當前請求Content-Type的值不被支持。其實是我們發起了"application/json"的類型請求導致的。這裏涉及到一個概念:預檢請求(preflight request),請看下面"預檢請求"的介紹。

3. Access-Control-Allow-Methods

是爲了防止出現以下錯誤:

Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

4.給OPTIONS 添加 204的返回

是爲了處理在發送POST請求時Nginx依然拒絕訪問的錯誤

發送"預檢請求"時,需要用到方法 OPTIONS ,所以服務器需要允許該方法。

預檢請求(preflight request)

其實上面的配置涉及到了一個W3C標準:CROS,全稱是跨域資源共享 (Cross-origin resource sharing),它的提出就是爲了解決跨域請求的。

跨域資源共享(CORS)標準新增了一組 HTTP 首部字段,允許服務器聲明哪些源站有權限訪問哪些資源。另外,規範要求,對那些可能對服務器數據產生副作用的HTTP 請求方法(特別是 GET 以外的 HTTP 請求,或者搭配某些 MIME 類型的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發起一個預檢請求(preflight request),從而獲知服務端是否允許該跨域請求。服務器確認允許之後,才發起實際的 HTTP 請求。在預檢請求的返回中,服務器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關數據)。
其實Content-Type字段的類型爲application/json的請求就是上面所說的搭配某些 MIME 類型的 POST 請求,CORS規定,Content-Type不屬於以下MIME類型的,都屬於預檢請求:

application/x-www-form-urlencoded
multipart/form-data
text/plain

所以 application/json的請求 會在正式通信之前,增加一次"預檢"請求,這次"預檢"請求會帶上頭部信息

Access-Control-Request-Headers: Content-Type:
OPTIONS /api/test HTTP/1.1
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
… 省略了一些

服務器迴應時,返回的頭部信息如果不包含Access-Control-Allow-Headers: Content-Type則表示不接受非默認的的Content-Type。即出現以下錯誤:

Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

方法二:反向代理

上面已經說到,禁止跨域問題其實是瀏覽器的一種安全行爲,而現在的大多數解決方案都是用標籤可以跨域訪問的這個漏洞或者是技巧去完成,但都少不了目標服務器做相應的改變,而我最近遇到了一個需求是,目標服務器不能給予我一個header,更不可以改變代碼返回個script,所以前5種方案都被我否決掉。最後因爲我的網站是我自己的主機,所以我決定搭建一個nginx並把相應代碼部署在它的下面,由頁面請求本域名的一個地址,轉由nginx代理處理後返回結果給頁面,而且這一切都是同步的。

關於nginx的一些基本配置和安裝請看我的另一篇博客,下面直接講解如何配置一個反向代理。
https://blog.csdn.net/ouyida3/article/details/86763553

首先找到nginx.conf或者nginx.conf.default 或者是default裏面的這部份。

其中server代表啓動的一個服務,location 是一個定位規則。

location /{   #所有以/開頭的地址,實際上是所有請求
  root  html     #去請求../html文件夾裏的文件,其中..的路徑在nginx裏面有定義,安裝的時候會有默認路徑,詳見另一篇博客
  index  index.html index.htm  #首頁響應地址
}

從上面可以看出location是nginx用來路由的入口,所以我們接下來要在location裏面完成我們的反向代理。

假如我們我們是 www.a.com/html/msg.html 想請求 www.b.com/api/?method=1&para=2;

我們的ajax:

var url = 'http://www.b.com/api/msg?method=1&para=2';
<br>$.ajax({
type: "GET",
url:url,
success: function(res){..},
....
})

上面的請求必然會遇到跨域問題,這時我們需要修改一下我們的請求url,讓請求發在nginx的一個url下。

var url = 'http://www.b.com/api/msg?method=1&para=2'var proxyurl = 'msg?method=1&para=2'//假如實際地址是 www.c.com/proxy/html/api/msg?method=1&para=2; www.c.com是nginx主機地址
 $.ajax({
type: "GET",
url:proxyurl,
success: function(res){..},
....
})  

再在剛纔的路徑中匹配到這個請求,我們在location下面再添加一個location。

location ^~/proxy/html/{
  rewrite ^/proxy/html/(.*)$ /$1 break;
  proxy_pass http://www.b.com/;
}

以下做一個解釋:

1.’^~ /proxy/html/ ’

就像上面說的一樣是一個匹配規則,用於攔截請求,匹配任何以 /proxy/html/開頭的地址,匹配符合以後,停止往下搜索正則。

2.rewrite ^/proxy/html/(.*)$ /$1 break;

代表重寫攔截進來的請求,並且只能對域名後邊的除去傳遞的參數外的字符串起作用,例如www.c.com/proxy/html/api/msg?method=1&para=2重寫。只對/proxy/html/api/msg重寫。

rewrite後面的參數是一個簡單的正則 ^/proxy/html/(.*)$ ,$1代表正則中的第一個(),$2代表第二個()的值,以此類推。

break代表匹配一個之後停止匹配。

3.proxy_pass

既是把請求代理到其他主機,其中 http://www.b.com/ 寫法和 http://www.b.com寫法的區別如下:

  • 不帶/
location /html/
{
  proxy_pass http://b.com:8300; 
}
  • 帶/
location /html/ 
{ 
    proxy_pass http://b.com:8300/; 
}

上面兩種配置,區別只在於proxy_pass轉發的路徑後是否帶 “/”。

針對情況1,如果訪問url = http://server/html/test.jsp, 則被nginx代理後,請求路徑會便問http://proxy_pass/html/test.jsp,將test/ 作爲根路徑,請求test/路徑下的資源。

針對情況2,如果訪問url = http://server/html/test.jsp, 則被nginx代理後,請求路徑會變爲 http://proxy_pass/test.jsp,直接訪問server的根資源。

修改配置後重啓nginx代理就成功了。

完結。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章