因爲瀏覽器同源策略,凡是發送請求url的協議、域名、端口三者之間任意一個與當前頁面地址不一樣即爲跨域。存在跨域的狀況:javascript
網絡協議不一樣,如http協議訪問https協議。php
端口不一樣,如80端口訪問8080端口。前端
域名不一樣,如qianduanblog.com訪問baidu.com。java
子域名不一樣,如abc.qianduanblog.com訪問def.qianduanblog.com。nginx
域名和域名對應ip,如www.a.com訪問20.205.28.90.ajax
請求的數據是XHR(XMLHttpRequest)瀏覽器會有跨域的限制。若是不是XHR類型的則不會限制
apache
這裏舉兩個常見的例子:json
1.咱們知道一幫瀏覽器都使用cookie去儲存咱們的用戶登陸信息。若是容許跨域訪問,那麼別的網站只須要一段腳本就能夠獲取你的cookie,從而冒充你的身份去登陸網站,形成很是大的安全問題。api
2.假設如今有兩個不一樣域,若是沒有這一安全策略,那麼當用戶在訪問a.com時,a.com的一段腳本就能夠在不加載b.com的頁面而隨意修改或者獲取b.com上面的內容。這樣將會致使b.com頁面的頁面發生混亂。跨域
其實跨域的解決辦法從本質上去區分,能夠劃分爲兩類。
第一類:被調用方
第二類:調用方
接下來咱們就開始基於這兩種類型去解決跨域問題。
這裏在說明代碼以前,咱們要先知道在咱們的請求中能夠分爲兩種請求:(1)簡單請求(2)非簡單請求
簡單請求:
在heard裏面無定義頭
Content-Type爲一下幾種:
text/plain
multipart/from-data
application/x-www-form-urlencoded
常見非簡單請求:
put,delete方法的ajax請求
發送json格式的ajax請求
帶自定義頭的ajax請求複製代碼
簡單請求與非簡單請求的區別
簡單請求:瀏覽器會先自行後判斷
非簡單請求:瀏覽器先判斷後執行(OPTIONS)
複製代碼
所以咱們在訪問非簡單請求的時候,咱們能夠看到發送的方法會是opttions。意思就是服務器先詢問服務是否存在相關資源。當存在相關資源的時候才能正常返回。
接下來咱們就能夠開展被調用方的解決方案:
什麼是JSONP?
Jsonp(JSON with Padding) 是 json 的一種"使用模式",可讓網頁從別的域名(網站)那獲取資料,即跨域讀取數據。
下面咱們展現JSONP的使用案例
#這裏咱們模仿ajax進行一個跨越訪問,其datatype須要設置爲jsonp
#裏面的jsonp,設置回調函數的名稱爲callback
<script type="text/javascript">
$.ajax({
url:"http://crossdomain.com/services.php",
dataType:'jsonp',
data:'',
jsonp:'callback',
success:function(result) {
for(var i in result) {
alert(i+":"+result[i]);//循環輸出a:1,b:2,etc.
}
},
timeout:3000
});
</script>
複製代碼
#這是後臺返回數據的方法
#能夠看出其實就是把返回的數據再包含在與前端的回調函數的名字同樣的函數體便可
function api_jsonp_encode($json)
{
if (!empty($_GET['callbak'])) {
return $_GET['callbak'] . '(' . $json . ')'; // jsonp
}
return $json; // json
}複製代碼
jsonp其實做爲一種跨域的解決手段其實存在比較明顯的缺陷:
1.服務器須要改動代碼
2.只支持get方法
3.發送的不是XHR複製代碼
設置頭文件:
//設置請求域名
header('Access-Control-Allow-Origin:*');
//設置請求頭
header("Access-Control-Allow-Headers:Origin, X-Requested-With, Content-Type, Accept, Authorization");
//設置請求方法
header('Access-Control-Allow-Methods:GET, POST, PUT,DELETE,OPTIONS,PATCH');
header('Access-Control-Allow-Methods:*');
//設置預檢命令的緩存頭
header('Access-Control-Max-Age:3600');
#這裏說明一下,*號表明沒有限制全匹配。所以你能夠看到們設置的請求域名,還有請求方法都是有*號配置。
#設置御檢命令緩存頭就是讓瀏覽器當發送一次御檢命令後,後期就能夠在緩存時間內直接使用緩存,而不須要再次請求。其第二個參數爲緩存時間
複製代碼
可是咱們直接上述的配置,只能知足通常狀況。由於若是當跨域訪問的時候攜帶cookie,或者自定義頭的時候咱們仍是不能成功跨域的所以。針對這兩種狀況,咱們能夠作出如下的調整修改
帶Cookie的跨域
//設置容許攜帶Cookie
header("Access-Control-Allow-Credentials: true");
//須要設置與服務器匹配的域名,不能使用*
header('Access-Control-Allow-Origin:http://www.xxx.com');
#若是可能須要匹配多個容許域名,能夠參考下面的動圖作法
//經過系統變量獲取origin
$origin = isset($_SERVER['HTTP_ORIGIN'])? $_SERVER['HTTP_ORIGIN'] : '';
//設置容許數組列表
$allow_origin = array(
'http://client1.runoob.com',
'http://client2.runoob.com'
);
//判斷是否存在於數組中
if(in_array($origin, $allow_origin)){
header('Access-Control-Allow-Origin:'.$origin);
}
複製代碼
自定義請求頭的跨域處理
通常狀況下可使用如下的代碼設置請求頭
header("Access-Control-Allow-Headers:Origin, X-Requested-With, Content-Type, Accept, Authorization");
可是若是不生效。能夠把對應的請求頭也寫進去例如:
header("Access-Control-Allow-Headers:Origin, X-Requested-With, Content-Type, Accept, Authorization,test-header");
複製代碼
以上就是經過被調用方設置header頭去解決對應的跨域問題。
可是上述的方法仍是存在必定的侷限性。由於加header頭本質上仍是會改動了咱們的代碼。那麼咱們還有沒有更好的方式呢?其實咱們能夠基於服務器的改造
ngix的配置
server {
listen 80; #監聽80端口
server_name demo.com; #本地域名
location / {
proxy_pass http://XXXX.com; #把全部請求都轉發到此須要提供服務的端口下
add_header Access-Control-Allow-Headers $http_access_control_request_headers'; add_header Access-Control-Allow-Origin $http_origin; add_header Access-Control-Allow-Methods *; add_header Access-Control-Max-Age 3600; add_header Access-Control-Allow-Credentials true; #判斷若是進入的是與預檢查 if ($request_method = OPTIONS){ return 200; } } } 複製代碼
.apache配置
//修改vhost文件
<virtualhost *:80="">
DocumentRoot "C:/htdocs/demo" #目錄路徑
ServerName demo.com #域名
##ErrorLog "logs/dummy-host.localhost-error.log"
##CustomLog "logs/dummy-host.localhost-access.log" common
#設置轉發(須要開啓 proxy_module,proxy_http)
ProxyPass/ http://XXXX.com
#把請求頭的Access-Controller-Request-Headerss值返回到Access-Control-Allow-Headers
Header always set Access-Control-Allow-Headers "expr=%{req:Access-Controller-Request-Headers}"
#把請求頭的orgin值返回到Access-Control-Allow-Orgin
Header always set Access-Control-Allow-Orgin "expr=%{req:orgin}"
Header always set Access-Control-Allow-Methods "*"
Header always set Access-Control-Allow-Credentials "true"
Header always set Access-Control-Max-Age "3600"
#處理預檢命令OPTIONS,直接返回204(開啓 headers_module,rewrite_module)
RewriteEngine On
RewriteCond%{REQUEST_METHOD}OPTIONS
RewriteRule^(.*)$"/"[R=204,L]
</virtualhost>
複製代碼
以上的服務器配置就是用於跨域請求的被調用方的配置
在通常開發中咱們解決的思路就是使用反向代理。而配置反向代理只能經過咱們的服務器去訪問對方的服務器,從而繞過瀏覽器的這一個檻。
nginx:
server {
listen 80; #監聽80端口
server_name demo.com; #本地域名
#後期全部的調用接口都須要使用該地址
#例如:咱們訪問http://aa.com/index/login
#改成:/ajaxserver/index/login
location/ajaxserver {
proxy_pass http://XXXX.com; #須要跨域訪問接口的的地址
}
}複製代碼
<virtualhost *:80="">
DocumentRoot "C:/htdocs/demo" #目錄路徑
ServerName demo.com #域名
##ErrorLog "logs/dummy-host.localhost-error.log"
##CustomLog "logs/dummy-host.localhost-access.log" common
#後期全部的調用接口都須要使用該地址
#例如:咱們訪問http://aa.com/index/login
#反向代理設置
ProxyPass /ajaxserver http://XXXX.com
</virtualhost>複製代碼