1. 程式人生 > 其它 >JavaWeb跨域問題常用解決辦法彙總

JavaWeb跨域問題常用解決辦法彙總

技術標籤:跨域

一.問題丟擲

前後端除錯過程中,瀏覽器經常出現下面的錯誤,出現Access-Control-Allow-Origin的字眼,就是跨域問題

二.什麼是跨域

當一個請求url的協議、域名、埠三者之間任意一個與當前頁面url不同即為跨域

當前頁面url被請求頁面url是否跨域原因
http://www.test.com/http://www.test.com/index.html同源(協議、域名、埠號相同)
http://www.test.com/https://www.test.com/index.html跨域協議不同(http/https)
http://www.test.com/http://www.baidu.com/跨域主域名不同(test/baidu)
http://www.test.com/http://blog.test.com/跨域子域名不同(www/blog)
http://www.test.com:8080/http://www.test.com:7001/跨域埠號不同(8080/7001)

三.為什麼會出現跨域問題

出於瀏覽器的同源策略限制。同源策略(Sameoriginpolicy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。可以說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。同源策略會阻止一個域的javascript指令碼和另外一個域的內容進行互動。所謂同源(即指在同一個域)就是兩個頁面具有相同的協議(protocol),主機(host)和埠號(port)

非同源限制:

【1】無法讀取非同源網頁的 Cookie、LocalStorage 和 IndexedDB

【2】無法接觸非同源網頁的 DOM

【3】無法向非同源地址傳送 AJAX 請求

四.跨域常用解決方法

1.JSONP

JSONP 是伺服器與客戶端跨源通訊的常用方法。最大特點就是簡單適用,相容性好(相容低版本IE),缺點是隻支援get請求,不支援post請求。

核心思想:網頁通過新增一個<script>元素,向伺服器請求 JSON 資料,伺服器收到請求後,將資料放在一個指定名字的回撥函式的引數位置傳回來

  • jQuery ajax:
$.ajax({
    url: 'http://www.test.com:8080/login',
    type: 'get',
    dataType: 'jsonp',  // 請求方式為jsonp
    jsonpCallback: "handleCallback",    // 自定義回撥函式名
    data: {}
});
  • Vue.js
this.$http.jsonp('http://www.domain2.com:8080/login', {
    params: {},
    jsonp: 'handleCallback'
}).then((res) => {
    console.log(res); 
})

2.CORS

CORS 是跨域資源分享(Cross-Origin Resource Sharing)的縮寫。它是 W3C 標準,屬於跨源 AJAX 請求的根本解決方法。

1、普通跨域請求:只需伺服器端設定Access-Control-Allow-Origin

2、帶cookie跨域請求:前後端都需要進行設定

前端設定:

根據xhr.withCredentials欄位判斷是否帶有cookie

  • jQuery ajax

$.ajax({
   url: 'http://www.test.com:8080/login',
   type: 'get',
   data: {},
   xhrFields: {
       withCredentials: true    // 前端設定是否帶cookie
   },
   crossDomain: true,   // 會讓請求頭中包含跨域的額外資訊,但不會含cookie
});
 
 
  • vue-resource

Vue.http.options.credentials = true
  • axios

axios.defaults.withCredentials = true

服務端設定:

伺服器端對於CORS的支援,主要是通過設定Access-Control-Allow-Origin來進行的。如果瀏覽器檢測到相應的設定,就可以允許Ajax進行跨域的訪問。

  • Java後臺
/*
 * 匯入包:import javax.servlet.http.HttpServletResponse;
 * 介面引數中定義:HttpServletResponse response
 */
 
// 允許跨域訪問的域名:若有埠需寫全(協議+域名+埠),若沒有埠末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); 
 
// 允許前端帶認證cookie:啟用此項後,上面的域名不能為'*',必須指定具體的域名,否則瀏覽器會提示
response.setHeader("Access-Control-Allow-Credentials", "true"); 
 
// 提示OPTIONS預檢時,後端需要設定的兩個常用自定義頭
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");

3.nginx代理

使用Nginx轉發請求。把跨域的介面寫成調本域的介面,然後將這些介面轉發到真正的請求地址。

安裝好Nginx後,修改nginx.conf檔案,把預設的server配置註釋掉。

在下面增加:

server{
    # 監聽9099埠
    listen 9099;
    # 域名是localhost
    server_name localhost;
    #凡是localhost:9099/api這個樣子的,都轉發到真正的服務端地址http://localhost:9871 
    location ^~ /api {
        proxy_pass http://localhost:9871;
    }    
}

為什麼不直接監聽9871,因為nginx監聽9871,就會佔用此埠,本地開發就不能以9871埠啟用

4.vue代理

在vue開發中實現跨域:在vue專案根目錄下找到vue.config.js檔案(如果沒有該檔案則自己建立),在proxy中設定跨域

devServer: {
    proxy: {  //配置跨域
      '/api': {
        target: 'http://121.121.67.254:8185/',  //這裡後臺的地址模擬的;應該填寫你們真實的後臺介面
        changOrigin: true,  //允許跨域
        pathRewrite: {
          /* 重寫路徑,當我們在瀏覽器中看到請求的地址為:http://localhost:8080/api/core/getData/userInfo 時
            實際上訪問的地址是:http://121.121.67.254:8185/core/getData/userInfo,因為重寫了 /api
           */
          '^/api': '' 
        }
      },
    }
  },

在建立axios例項的時候將baseURL設定為/api ,這時候我們的跨域就已經完成了。

// 建立axios例項
const service = axios.create({
  baseURL: /api, // api的base_url
  timeout: 60000 // 請求超時時間
})

在vue中使用proxy進行跨域的原理是:將域名傳送給本地的伺服器(啟動vue專案的服務,loclahost:8080),再由本地的伺服器去請求真正的伺服器。

問:proxyTable 裡面的pathRewrite裡面的‘^/api’:'' 什麼意思?

答:用代理, 首先你得有一個標識, 告訴他你這個連線要用代理. 不然的話, 可能你的 html, css, js這些靜態資源都跑去代理. 所以我們只要介面用代理, 靜態檔案用本地.

'/api': {}, 就是告訴node, 我介面只要是'/api'開頭的才用代理.所以你的介面就要這麼寫/api/xx/xx. 最後代理的路徑就是http://xxx.xx.com/api/xx/xx.

可是不對啊, 我正確的介面路徑裡面沒有/api啊. 所以就需要pathRewrite,用''^/api'':'', 把'/api'去掉, 這樣既能有正確標識, 又能在請求介面的時候去掉api.

5.nginx配置

location ~* \.(eot|ttf|woff)$ {
   add_header Access-Control-Allow-Origin '*';
}

Access-Control-Allow-Origin:*表示允許任何域名跨域訪問。

擴充套件

server {
  listen        80;
  server_name   uedbox.com;
 
 
  location / {
 
    # Simple requests
    if ($request_method ~* "(GET|POST)") {
      add_header "Access-Control-Allow-Origin"  *;
    }
 
    # Preflighted requests
    if ($request_method = OPTIONS ) {
      add_header "Access-Control-Allow-Origin"  *;
      add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD";
      add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept";
      return 200;
    }
 
    ....
    # Handle request
    ....
  }
}

還可以動態配置跨域方案

set $cors '';
if ($http_origin ~ '^https?://(localhost|www\.yourdomain\.com|www\.yourotherdomain\.com)') {
        set $cors 'true';
}
 
if ($cors = 'true') {
        add_header 'Access-Control-Allow-Origin' "$http_origin" always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;
        # required to be able to read Authorization header in frontend
        #add_header 'Access-Control-Expose-Headers' 'Authorization' always;
}
 
if ($request_method = 'OPTIONS') {
        # Tell client that this pre-flight info is valid for 20 days
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;
        return 204;
}