一、瞭解什麼是同源策略
同源策略(Same origin policy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。可以說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。
同源策略,它是由Netscape提出的一個著名的安全策略。
現在所有支持JavaScript 的瀏覽器都會使用這個策略。
所謂同源是指,域名,協議,端口相同。
當一個瀏覽器的兩個tab頁中分別打開來 百度和谷歌的頁面,
瀏覽器的百度tab頁執行一個腳本的時候會檢查這個腳本是屬於哪個頁面的,
即檢查是否同源,只有和百度同源的腳本纔會被執行。如果非同源,那麼在請求數據時,瀏覽器會在控制檯中報一個異常,提示拒絕訪問。
但是如果我百度想要獲得谷歌頁面的腳本或者某個資源怎麼辦,這時候就需要發起跨域請求。
二、瞭解什麼是CORS
概念
跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不同源服務器上的指定的資源。當一個資源從與該資源本身所在的服務器不同的域或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。
比如:網站A中有些圖片是通過img標籤中的src請求B網站的某些圖片資源,這就是一個簡單的跨域請求。
出於安全原因,瀏覽器限制從腳本內發起的跨源HTTP請求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 這意味着使用這些API的Web應用程序只能從加載應用程序的同一個域請求HTTP資源,除非使用CORS頭文件。
通俗的理解就是,實現跨域的方式主要就是在請求和響應的頭部添加額外的信息,告訴瀏覽器和服務器這是一個跨域請求,不能攔截我,不能阻止我從服務器拿到數據。
主要的三種跨域訪問場景
在瞭解跨域訪問場景之前,我們先了解一下有關跨域的請求和響應的頭部字段。對於我們理解跨域場景有很大的作用。
HTTP 請求首部字段:
- Origin:表明預檢請求或實際請求的源站。
- Access-Control-Request-Method:用於預檢請求,將實際請求所使用的 HTTP 方法告訴服務器。
- Access-Control-Request-Headers:用於預檢請求,將實際請求所攜帶的首部字段告訴服務器。
HTTP 響應首部字段:
- Access-Control-Allow-Origin:允許訪問該資源的外域 URI。對於不需要攜帶身份憑證的請求,服務器可以指定該字段的值爲通配符,表示允許來自所有域的請求。
- Access-Control-Expose-Headers:服務器把允許瀏覽器訪問的頭放入白名單。
- Access-Control-Max-Age:指定了preflight請求的結果能夠被緩存多久。
- Access-Control-Allow-Credentials:指定了當瀏覽器的credentials設置爲true時是否允許瀏覽器讀取response的內容。當用在對preflight預檢測請求的響應中時,它指定了實際的請求是否可以使用credentials。
- Access-Control-Allow-Methods:用於預檢請求的響應。其指明瞭實際請求所允許使用的 HTTP 方法。
- Access-Control-Allow-Headers:用於預檢請求的響應。其指明瞭實際請求中允許攜帶的首部字段。
簡單請求
發送請求時,請求的header中 Origin 表示這個請求是源於哪個網站,在響應的header中, Origin 和 Access-Control-Allow-Origin 完成最簡單的訪問控制,其中設置服務端響應的Access-Control-Allow-Origin字段值,可以控制哪些請求訪問服務器。
預檢請求
首先使用 OPTIONS 方法發起一個預檢請求到服務器,以獲知服務器是否允許該實際請求。"預檢請求“的使用,可以避免跨域請求對服務器的用戶數據產生未預期的影響。
簡單來說,在向服務器請求資源前,我們先發送一個請求過去告訴服務器,我等一會兒(這個一會兒很快)要從你的這裏拿點東西,通過什麼方式拿,來拿的時候我還會帶些東西。服務器以此決定你等一會兒的請求(實際請求)是否允許。
預檢請求發送時頭部會攜帶兩個字段Access-Control-Request-Method和Access-Control-Request-Headers,前者告訴服務器,實際請求將使用的方法,後者告訴服務器實際請求將攜帶的自定義請求首部字段。
預檢請求的響應的頭部中將攜帶以下字段:
- Access-Control-Allow-Origin:允許請求資源的網站
- Access-Control-Allow-Methods:允許服務端發送請求的方法
- Access-Control-Allow-Headers:允許服務器請求中攜帶的字段
- Access-Control-Max-Age:表明請求響應的有效時間
附帶身份憑證的請求
CORS可以基於HTTP cookies 和 HTTP 認證信息發送身份憑證。附帶身份憑證就是發送請求的同時設置cookie,把cookie信息發送給服務器。
**值得我們注意的一點:**對於附帶身份憑證的請求,服務器不得設置 Access-Control-Allow-Origin 的值爲“*”。
這是因爲請求的首部中攜帶了 Cookie 信息,如果 Access-Control-Allow-Origin 的值爲“*”,請求將會失敗。應當設爲對應的允許訪問的網站的url
參考文檔: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
三、瞭解傳統的跨域方式——jsonp
jsonp的產生
在ajax請求中,出於瀏覽器的同源策略對於跨域的請求是一概禁止的。但是聰明的程序員們發現Web頁面上調用js文件不受是否跨域的影響,不僅如此,我們還發現凡是擁有”src”這個屬性的標籤都擁有跨域的能力,比如
發現問題之後自然是解決問題,既然知道上面的那些方式不受跨域限制,那麼我們可不可以在服務器上把數據裝進js格式的文件裏,供客戶端進行處理呢?
那什麼類型的數據可以被js支持,同時便於前後臺的數據傳輸呢?當然是json字符串啦。
那麼我們便可以在服務端生成js格式的文件,裏面用json存儲數據,在客戶端也就是瀏覽器便可以通過與調用腳本一樣的方式去調用這些數據,然後就可以爲所欲爲了。
爲了便於客戶端使用數據,逐漸形成了一種非正式傳輸協議,人們把它稱作JSONP,該協議的一個要點就是允許用戶傳遞一個callback參數給服務端,然後服務端返回數據時會將這個callback參數作爲函數名來包裹住JSON數據,這樣客戶端就可以隨意定製自己的函數來自動處理返回數據了。
jsonp(JSON with Padding) 是 json 的一種"使用模式",可以跨域讀取數據。
jsonp的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- jsonp實現跨域-->
<button id="btnOne">click me</button><br/>
<script src="../js/jquery.js"></script>
<script>
$(document).ready(function () {
// jsonp完整請求http://localhost:8080/aa?callBack=callBack&_=1543219203733
$("#btnOne").click(function(){
$.ajax({
url: "http://localhost:8080/getJsonp",
type: "GET",
dataType: "jsonp", //指定服務器返回的數據類型
jsonp: "callBack", //指定方法參數名稱,默認爲callBack
jsonpCallback: "callBack", //自定義的jsonp回調函數名稱,默認爲jQuery自動生成的隨機函數名,
success: function (data) {
console.info("調用success");
}
});
});
</script>
</body>
</html>
ajax和jsonp其實本質上是不同的東西。ajax的核心是通過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態添加,因爲jsonp和ajax的調用方式高度類似,所以query把jsonp作爲ajax的一種形式進行了封裝。但是現在這種方式不推薦使用,因爲它是一種“取巧”的方式,不安全。
四、自定義過濾器實現簡單的跨域訪問控制
@WebFilter(filterName = "CorsFilter")
public class CorsFilter implements Filter {
@Override
public void destroy() {
}
String[] allowOrigin = null;
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String origin = request.getHeader("Origin");
if (origin != null && !origin.isEmpty()){
for (String o: allowOrigin) {
if ("*".equals(o) || o.equals(origin)){
response.setHeader("Access-Control-Allow-Origin", origin);
break;
}
}
}
chain.doFilter(req, resp);
}
@Override
public void init(FilterConfig config) throws ServletException {
String webFilter = config.getInitParameter("webFilter");
if (webFilter != null) {
if ("*".equals(webFilter)) {
allowOrigin = new String[]{"*"};
} else {
allowOrigin = webFilter.split(",");
}
}
}
}
web.xml配置
<!--簡單的跨域用使用自定義過濾器實現-->
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>com.lzx.vo_filter.CorsFilter</filter-class>
<init-param>
<param-name>webFilter</param-name>
<param-value>*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
這裏是我們是對簡單的跨域請求進行過濾。此外還可以嘗試國旅一下預檢請求和帶憑證的請求。
五、SpringMVC跨域配置
從Spring MVC 4.2 開始增加支持跨域訪問,使用4.2之後的版本才能採用以下配置
註解配置
@CrossOrigin
@RestController
public class CroController {
//...
}
spring-mvc.xml配置文件配置
<!--全局配置有一定的侷限性,開放了所有的訪問-->
<mvc:cors>
<!--允許所有的網站進行跨域訪問-->
<!--<mvc:mapping path="/**"/>-->
<!--詳細配置符合條件的跨域訪問-->
<mvc:mapping path="/**"
allowed-headers="Accept,Accept-Language,Content-Language,Content-Type"
allowed-methods="GET,POST,DELETE,PUT"
allowed-origins="http://127.0.0.1:8081"
allow-credentials="true"
/>
</mvc:cors>