1、背景前端
由於是先後端分離開發,因此跨域問題一直都遇到,但之前一直使用的解決方案是經過代碼控制設置response.setheader來解決的,這是在百度搜索獲得的最多的一個結果,大部分的文章博客都是用這個方案來解決的,誠然它也一直在起做用,直到我最近開發一個新項目再次遇到跨域問題,明明已經設置了response仍是會發生,我便開始深刻探究,因而就有了一下內容,這是一遍真正解決跨域問題的研究成果,不是百度上人云亦云的複製粘貼來的。spring
2、跨域問題的產生json
跨域問題在先後端分離開發的場景下常常發生,那麼在什麼狀況下會肯定發生跨域問題呢?就是先後端不一樣源的時候,同源須要知足三個條件:後端
1)協議相同 (http https這種)跨域
2)域名相同tomcat
3)端口相同服務器
一般場景下咱們的先後端雖然部署在同一個服務器,但通常都是前端放在NGINX 後端放在tomcat 端口是不一樣的,因而就產生了協議相同、域名相同可是端口不一樣的跨域問題,此處說個題外話,想要避免跨域問題把先後端都放在同一個tomcat裏就好了。app
3、CORS的兩種請求方式前後端分離
解決跨域有幾種方案,通常最經常使用的是CORS,由於此方案不須要前端改動,事實上前面提到的response.setheader的方式也是CORS,本文也只針對CORS進行講解。url
CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request),只要同時知足如下兩大條件,就屬於簡單請求:
(1) 請求方法是如下三種方法之一:
HEAD
GET
POST
(2)HTTP的頭信息不超出如下幾種字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限於三個值application/x-www-form-urlencoded、 multipart/form-data、text/plain
不知足以上條件的就屬於非簡單請求,我稱之爲複雜請求。
4、兩種請求方式的實戰解決方案
回顧下背景,之前能夠解決跨域的方式爲什麼如今就不起做用了呢?緣由就是出在了這不一樣的請求方式上,之前的項目都是簡單請求,而如今的項目是複雜請求,解決措施不同了,
簡單請求
簡單請求能直接進入到接口裏執行它的內部邏輯,執行完正常返回響應,但若是想要響應的結果被正確接收,就須要在響應頭裏加上跨域的許可Access-Control-Allow,響應頭就是Response Header,關於跨域許可的幾個參數有:
等等,其中簡單請求必需要有的參數是Access-Control-Allow-Origin (容許跨域的源),其餘參數請自行查詢做用,這裏不作開展,所以解決簡單請求的跨域的問題只須要在響應頭加上 Access-Control-Allow-Origin 參數,例子:
String origin = request.getHeader("Origin");
if (origin == null) {
origin = request.getHeader("Referer");
}
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Allow-Headers", "*");
複雜請求
複雜請求不會直接進入接口內部執行接口的邏輯,而是會先發送OPTIONS 預檢請求,若是預檢請求不能被正確響應,就不會進入到方法內部執行邏輯,因此處理簡單請求在代碼裏設置響應頭的方法就不適用了,請求根本達到不了方法內部,因而咱們須要在執行方法裏的代碼以前,就處理好響應頭,由此不難聯想到使用攔截器就能很好的解決問題
複雜請求的處理原理跟簡單請求是同樣的,只要設置好響應頭就能夠了,不一樣的是在哪一個層設置響應頭,固然對於簡單請求使用複雜請求的處理方式也一樣適用,複雜請求處理的兩種方式:
1)使用攔截器,設置好響應頭,其中必須的跨域參數仍是Access-Control-Allow-Origin,若是有更改請求頭的話也須要帶上Access-Control-Allow-Headers(容許的請求頭),事實上會引發複雜請求的大部分場景都是由於更改了請求頭,發送json數據時須要設置請求頭contentType=application/json。 雖然通常的場景只須要這兩個參數就夠了但仍是建議把全部參數都寫全,緣由可看下面。攔截器例子:
public class HeadersCORSFilter
implements Filter
{
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse)servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(request, servletResponse);
}
public void init(FilterConfig arg0) throws ServletException {
System.out.println("攔截器初始化");
}
}
2)使用@CrossOrigin,spring能流行的一部分緣由我相信有標籤的功勞,使用spring標籤能幫助咱們更優雅簡潔的編寫代碼,因此當我瞭解到有能夠解決跨域問題的@CrossOrigin標籤時也是堅決果斷的用了,按照百度上大多數人的說法在方法上面加上@CrossOrigin標籤就能夠直接解決問題了,再不濟就加上@CrossOrigin(origins=」xx」)指明容許跨域的源就能夠了,但很遺憾,不管是哪一種方法對我都沒有做用,因而深刻研究@CrossOrigin,查看其源碼發現,除了allow-methods外其餘四個參數都是有默認值的(注:此處是基於org.springframework 4.23版本)
因而嘗試增長allow-methods屬性:
@CrossOrigin(methods = {RequestMethod.POST})
結果就真的起做用了,完美解決了跨域問題。網上還有些說法是在requestmapping上指定方法@RequestMapping(method = RequestMethod.GET),其原理是同樣的,由於@CrossOrigin會默認支持@RequestMapping聲明的全部源和方法類型。
5、結論
產生跨域問題不要着急,首先分析是否須要規避跨域問題,規避方法就是把先後端放在同一個源裏,沒法規避的,再分析是簡單請求仍是複雜請求,對於複雜請求是否須要規避,規避方法爲變複雜請求爲簡單請求,改變請求方式不使用超出範圍的請求頭等,沒法規避就處理複雜請求。