1. 程式人生 > 實用技巧 >Mybatis logImpl 學習Error

Mybatis logImpl 學習Error

跨域資源共享 CORS 詳解

轉載連結:
跨域資源共享 CORS 詳解
SpringBoot:處理跨域請求

一、簡介

1.1 跨域

URL的格式為:協議+域名+埠號+資源地址
示例:https://www.baidu.com/
只要協議、域名、埠號有一項不同,就可以認為是不同的域。不同的域之間互相訪問資源,就被稱為跨域。

1.2 CORS

CORS是一個w3c標準,全稱是跨域資源共享(Cross-origin resource sharing)。它允許瀏覽器向跨源伺服器發出XMLHttpRequest請求,從而克服AJAX只能同源使用的限制。

1.3 如何實現CORS

CORS需要瀏覽器和伺服器同時支援。目前所有的瀏覽器都支援該功能,IE瀏覽器不能低於IE10。整個CORS通訊過程,都是瀏覽器自動完成,不需要使用者參與。對於開發者來說,CORS通訊與同源的AJAX通訊沒有差別,程式碼完全一樣。瀏覽器一旦發現AJAX請求跨域,就會自動新增一些附加的頭資訊,有時還會多出一次附加的請求,但使用者不會有感覺。

因此,實現CORS通訊的關鍵是伺服器。只要伺服器實現了CORS介面,就可以跨源通訊。每一個頁面需要http相應中返回一個名為Access-Control-Allow-Origin響應頭來允許外域的站點訪問。即如果一個請求需要允許跨域訪問,則需要在http響應頭中設定Access-Control-Allow-Origin響應頭來決定需要允許哪些站點來訪問,假如需要允許 https://www.baidu.com/ 這個站點的請求跨域,則可以設定:Access-Control-Allow-Origin:https://www.baidu.com/

二、talk is easy,show me the code

既然已經知道了CORS可以解決跨域,也知道了如何設定。看下程式碼裡如何設定,其實在nginx層也可以處理的這裡暫時展示在程式碼裡如何解決跨域。

2.1 跨域的現象

有如下介面

package com.lucky.spring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * Created by zhangdd on 2020/9/4
 *
 * 測試跨域
 */
@Controller
@ResponseBody
public class CorsController {

    @GetMapping("/cross/fun")
    public String fun(){
        return "success";
    }
}

我們在 https://www.baidu.com/ 這個頁面執行如下指令碼,注意這裡是PUT請求

var url = 'http://127.0.0.1:8080/cross/fun';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.send();

  • 開啟任意網站,這裡以百度 https://www.baidu.com/ 為例
  • 按F12開啟除錯頁面,在console面板裡輸入上面的js指令碼
  • 輸入完之後按回車就可以返回結果
    • 我們看到返回一個錯誤 Access to XMLHttpRequest at 'http://127.0.0.1:8080/cross/fun' from origin 'https://www.baidu.com' 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. 從www.baidu.com這裡發起訪問127.0.0.1:8080的請求被CORS policy策略阻塞了,預請求的返回結果沒有通過,在請求頭裡沒有Access-Control-Allow-Origin這個內容。
  • 另外開啟network面板,看到兩次錯誤的請求
    • 一次是預請求 返回403,如下圖所示 OPTIONS 表示此次請求是預請求
    • 另外一個錯誤的請求就是每有傳送的資源請求,如果預請求通過這個請求會執行。如下圖所示:

2.2 解決跨域

看到了跨域現象,知道了如何解決跨域。這裡就來解決跨域。這裡是在Spring boot環境下處理跨域問題。解決思路如下:

  1. 實現WebMvcConfigurer介面
  2. 重寫addCorsMappings方法
package com.lucky.spring.config;

import com.lucky.spring.interceptor.WebInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Created by zhangdd on 2020/8/25
 */
@Configuration
public class WebConfigurer implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new WebInterceptor())
                .excludePathPatterns("/error");
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").
                allowedOrigins("https://www.baidu.com", "https://zhangdd.com")//必須欄位,允許跨域的域名,可以用*表示允許任何域名使用
        //.allowedMethods("*")//可選欄位,允許跨域的方法,使用*表示允許任何方法
        //.allowCredentials(true)//可選欄位,布林值,表示是否允許傳送cookie資訊
        //.allowedHeaders("*")//允許任何請求頭
        //.exposedHeaders("name")//可選欄位,指定響應頭裡的欄位資訊
        //.maxAge(1000) //可選欄位,用來指定本次預檢請求的有效期,單位為秒
        ;
    }
}

傳送如下程式碼,注意這裡改成了GET請求,因為訪問的介面是GetMapping。

var url = 'http://127.0.0.1:8080/cross/fun';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.send();

結果如下圖所示:通過了跨域請求

三、兩種請求

瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
只要同時滿足以下兩大條件,就屬於簡單請求。

  1. 請求方法是以下三種方法之一:
    1. HEAD
    2. GET
    3. POST
  2. HTTP的頭資訊不超過以下幾種欄位:
    1. Accept
    2. Accept-Language
    3. Content-Language
    4. Last-Event-ID
    5. Content-Type只限於三個值application/x-www-form-urlencodedmultipart/form-datatext/plain

凡是不同時滿足上面兩個條件,就屬於非簡單請求。瀏覽器對這兩種請求的處理,是不一樣的。

上面的演示流程,可以看出非簡單請求會發出一次預請求。

四、簡單請求

4.1 基本流程

對於簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在頭資訊之中,自動增加一個origin欄位。origin欄位用來說明,本次請求來自哪個源(協議+域名+埠)。伺服器根據這個值,決定是否同意這次請求。

如果origin指定的源,不在許可範圍內,伺服器會返回一個正常的HTTP迴應。瀏覽器發現,這個迴應的頭資訊中如果沒有包含Access-Control-Allow-Origin欄位,就知道出錯了,從而丟擲一個錯誤,被XMLHttpRequest的onerror回撥函式捕獲。注意,這種錯誤無法通過狀態嗎識別,因為HTTP響應的狀態有可能是200。

如果origin指定的域名在許可範圍內,伺服器返回的響應會多處幾個頭資訊欄位。

  • Access-Control-Allow-Origin

該欄位是必須的。它的值要麼是請求時Origin欄位的值,要麼是一個*,表示接受任意域名的請求。

  • Access-Control-Allow-Credentials

該欄位可選。它的值是一個布林值,表示是否允許傳送Cookie。預設情況下,Cookie不包括在CORS請求之中。設為true,即表示伺服器明確許可,Cookie可以包含在請求中,一起發給伺服器。這個值也只能設為true,如果伺服器不要瀏覽器傳送Cookie,刪除該欄位即可。

CORS請求預設不傳送Cookie和HTTP認證資訊。如果要把Cookie發到伺服器,一方面要伺服器同意,指定Access-Control-Allow-Credentials欄位。

另一方面,開發者必須在AJAX請求中開啟withCredentials屬性。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;


否則,即使伺服器同意傳送Cookie,瀏覽器也不會發送。或者,伺服器要求設定Cookie,瀏覽器也不會處理。但是,如果省略withCredentials設定,有的瀏覽器還是會一起傳送Cookie。這時,可以顯式關閉withCredentials。
> ```
xhr.withCredentials = false;

需要注意的是,如果要傳送Cookie,Access-Control-Allow-Origin就不能設為星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用伺服器域名設定的Cookie才會上傳,其他域名的Cookie並不會上傳,且(跨源)原網頁程式碼中的document.cookie也無法讀取伺服器域名下的Cookie。

  • Access-Control-Expose-Headers

該欄位可選。CORS請求時,XMLHttpRequest物件的getResponseHeader()方法只能拿到6個基本欄位:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他欄位,就必須在Access-Control-Expose-Headers裡面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar欄位的值。

五、非簡單請求

5.1 預檢請求

非簡單請求是那種對伺服器有特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type欄位的型別是application/json。

非簡單請求的CORS請求,會在正式通訊之前,增加一次HTTP查詢請求,稱為預檢請求preflight。

瀏覽器先詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些請求方法和頭資訊欄位。只有得到肯定答覆,瀏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。

var url = 'http://127.0.0.1:8080/cross/fun';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

上面程式碼中,在傳送HTTP請求是設定了一個自定義的頭資訊。瀏覽器發現,這是一個非簡單請求,就自動發出一個預檢請求,要求伺服器確認可以這樣請求。如下圖所示:

  • 預檢請求用的方法是OPTIONS,表示這個請求是用來詢問的。
  • 頭資訊裡面關鍵字origin表示請求來自哪個源。

除了origin欄位,預檢請求的頭資訊裡還包括兩個特殊欄位:

  • Access-Control-Request-Method

該欄位是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是GET

  • Access-Control-Request-Headers

該欄位是一個逗號分隔的字串,指定瀏覽器CORS請求會額外發送的頭資訊欄位,上例是X-Custom-Header

5.2 預檢請求的迴應

伺服器收到"預檢"請求以後,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers欄位以後,確認允許跨源請求,就可以做出迴應。

上面的HTTP迴應中,關鍵的是Access-Control-Allow-Origin欄位,表示htts://www.baidu.com可以請求資料。該欄位也可以設為星號,表示同意任意跨源請求。

如果伺服器否定了預檢請求,會返回一個正常的HTTP迴應,但是沒有任何CORS相關的頭資訊欄位。這時,瀏覽器就會認定,伺服器不同意預檢檢查,因此觸發一個錯誤,被XMLHttpRequest物件的onerror回撥函式捕獲。還有一個返回欄位沒有提到:

  • Access-Control-Max-Age

該欄位可選,用來指定本次預檢請求的有效期,單位為秒。上面結果中,有效期是20天(1728000秒),即允許快取該條迴應1728000秒(即20天),在此期間,不用發出另一條預檢請求。

到這裡,跨域相關的東西就描述完了。