一、什麼是跨域問題?
1.同源政策:協定+域名+端口一緻,否則就算跨域。(localhost與127.0.0.1也算跨域)
2.DOM同源政策:禁止對不同源頁面DOM進行操作
3.XmlHttpRequest同源政策:禁止使用XHR對象向不同源的伺服器位址發起HTTP請求
二、跨域解決方案之JSOP
1.原理:利用script标簽src屬性中的連結卻可以通路跨域的js腳本的特性,服務端不再傳回JSON格式的資料,而是傳回一段調用某個函數的js代碼,在src中進行了調用,這樣實作了跨域。
2.優點&局限性:沒有浏覽器相容性問題;隻支援GET方法
3.JSONP請求示例:
3.1、前端AJAX請求,注意type、dataType、jsonp字段(jsonp可以不指定,預設就是callback)
$.ajax({
type: "GET",
url: url,
dataType: "jsonp",
jsonp: "callback",
data: {
dataType: "1", //告訴伺服器端這是JSONP請求
json: json //業務請求資料,JSON格式
},
success: function(data) {
//請求成功後的處理
},
error: function(status) {
//請求失敗後的處理
}
})
3.2、後端傳回的資料格式處理
/**
* 将指定對象封裝成JSONPObject,錯誤時可以指定錯誤碼.
*
* @param pObj 被封裝對象
* @return 封裝後的JSONPObject
*/
public Object getJSONResultObj(Object pObj, Boolean pIsSuccess, String pMessage, String pErrorCode) {
HttpServletRequest request = this.getRequest();
String callBackFun = request.getParameter("callback");
//0:JSON、1:JSONP
String dataType = request.getParameter("dataType");
// 作成JSON處理結果對象
JSONResultVo result = new JSONResultVo();
result.setResult(pIsSuccess ? JSONResultVo.RESULT_SUCCESS : JSONResultVo.RESULT_FAIL);
result.setMessage(pMessage);
result.setErrorCode(pErrorCode);
result.setData(pObj);
if ("1".equals(dataType)) {
//回調方法未指定時
if (StringUtil.isEmpty(callBackFun)) {
result.setResult(JSONResultVo.RESULT_FAIL);
result.setMessage("請求錯誤:未指定callback");
}
return callBackFun + "(" + getJSON(result).toString() + ");";
} else {
return getJSON(result);
}
}
3.3、請求傳回的資料格式:
可以看到,傳回的一個調用jsonp1方法的JS腳本,傳入方法的參數才是我們真正想要的資料;提取資料并轉換成對象類型這些Jquery的ajax已經封裝好了,并作為success方法的參數,我們可以直接使用。
三、跨域解決方案之CORS
CORS分為簡單請求和非簡單請求兩種。
1、簡單請求:
1.1、簡單請求必需滿足的條件
①.請求方法:隻能是HEAD、GET、POST中的一種
②.請求頭必須在下面的範圍内:
· Accept
· Accept-Languange
· Content-Language
· Last-event-ID
· Content-Type(隻限于三個值application/x-www-form-urlencode、multipart/form-data、text/plain)
1.2、浏覽器發送的請求
請求頭增加Origin字段,該字段表示請求源的位址即目前頁面的域名(伺服器端一般以此判斷是否跨域)。
1.3、伺服器的響應資訊
①.Access-Control-Allow-Origin:允許跨域請求時,該值應該包含請求的Origin字段的值(隻能是單個),或者一個【*】 ,表示接受任意域名的請求。對于隻能設定單個請求源的限制,可以通過設定變量值動态設定Origin的值解決。
②.Access-Control-Allow-Credentials(可選):是否允許發送Cookie。如果要把Cookie發到伺服器,一方面要伺服器同意,指定Access-Control-Allow-Credentials字段為true,另一方面,開發者必須在AJAX請求中設定withCredentials屬性為true(此時Access-Control-Allow-Origin不能為 * )。
2、非簡單請求
2.1、用于哪些場合?
請求方法或頭資訊有特殊要求的;如請求方法是DELETE,或Content-Type字段的類型application/json。 2.2、 預檢
2.2.1、什麼是預檢
正式通信之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。浏覽器先詢問伺服器,目前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些HTTP動詞和頭資訊字段。隻有得到肯定答複,浏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。
2.2.2、預檢請求有什麼特殊的地方
(1)請求方法是OPTIONS;
(2)請求頭除了Origin字段,包含另外兩個特殊的字段Access-Control-Request-Method:請求使用哪種方法、Access-Control-Request-Headers:額外發送的頭資訊字段
(3)除了ccess-Control-Allow-Origin,響應頭包含一些特殊的字段:Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Allow-Credentials(可選)、Access-Control-Max-Age(可選)
2.2.3、預檢請求成功示例:
ajax請求資訊(正式請求)
$.ajax({
type: "DELETE",
url: url,
data: {
dataType: "2"
},
headers: "My-Custom-Header", //自定義頭
success: function(data) {
$(".result").text("ID:" + data.data.id + " NAME:" + data.data.name);
},
error: function(status) {
$.alert("您的網絡不給力哦");
}
})
下面是浏覽器自動發送的預檢請求
這個請求對應的正式請求的請求方法是DELETE,而且發送一個自定的頭my-custom-header,因為不滿足簡單請求的條件,是以浏覽器在發送正式請求之前發送的一個預請求。浏覽器會根據預請求傳回的一些頭資訊判斷是否有權通路該資源,通過的話才發送正式的請求,正式請求與簡單請求差不多。預請求有時效性,在預請求有效期内(可通過響應的Access-Control-Max-Age字段設定),不會重複發送預請求。
如果預檢沒通過,浏覽器會報下面額錯,告訴預檢失敗
2.2.4、SpringMVC支援CORS的三種方式:
(1)使用@CrossOrigin注解(4.2及以上)
@RequestMapping(value = "/{id}", produces = CHARSET_PRODUCES, method=RequestMethod.DELETE)
@ResponseBody
@CrossOrigin(origins="*",allowedHeaders="My-Custom-Header",methods=RequestMethod.DELETE)
public Object testCors(String json, @PathVariable("id")String id,
HttpServletResponse response, HttpServletRequest request) {
Map<String, String> data = new HashMap<String, String>();
data.put("id", id);
data.put("name", "測試");
return success(data);
}
(2)CORS全局配置:可以以path為機關定義多個映射(4.2及以上)
基于XML的配置
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="http://djf.eastallcar.com"
allowed-methods="POST"
allowed-headers="My-Custom-Header"
allow-credentials="true"
max-age="1800"/>
<mvc:mapping path="/**"
allowed-origins="*"
allowed-methods="GET,POST,DELETE"
allowed-headers="My-Custom-Header"
allow-credentials="true"
max-age="1800"/>
</mvc:cors>
基于JAVA代碼的配置
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://djf.eastallcar.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("My-Custom-Header1", "My-Custom-Header2")
.exposedHeaders("My-Custom-Header1", "My-Custom-Header2")
.allowCredentials(false).maxAge(3600);
}
}
(3)增加一個繼承自OncePerRequestFilter的過濾器
/**
* 跨域請求過濾器.
*/
public class CorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
response.setHeader("Access-Control-Allow-Origin", "http://djf.eastallcar.com");
response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "My-Custom-Header");
response.setHeader("Access-Control-Max-Age", "60");
filterChain.doFilter(request, response);
}
}
2.2.5、CORS個人使用體會:
(1)使用簡單請求還是非簡單請求是浏覽器根據請求方法和頭資訊決定的,與伺服器端無關;
(2)CORS是為了解決浏覽器端的跨域限制,不是解決伺服器資源安全的方式;可以認為浏覽器對跨域資源請求的流程是對資源請求的安全的一個二次校驗,這個校驗需要伺服器端提供支援情況(通過響應頭告訴浏覽器支援的請求源、方法、頭)。
(3)部分浏覽器的老版本不支援CORS,适用于對相容性要求不高的項目。