跨域問題簡單的說就是前台請求一個后台鏈接,發送請求的前台與后台的地址不在同一個域下,就會產生跨域問題。這里所指的域包括協議、IP地址、端口等。
1.跨域訪問安全問題
后端代碼:
package cn.qs.controller; import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.collections.MapUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/test") @RestController public class TestController { @GetMapping("/get") public Map<String, Object> get(@RequestParam Map<String, Object> condition) { if (MapUtils.isEmpty(condition)) { condition = new LinkedHashMap<>(); condition.put("param", null); } return condition; } }
前端代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <script type="text/javascript" src="js/jquery-1.8.3.js" ></script> <body> </body> <script> + function test() { $.getJSON("http://localhost:8088/test/get.html", {}, function(res) { console.log(res); }); }(); </script> </html>
結果:雖然后端正常響應,但是JS報錯,這就是跨域安全問題,如下:
js報錯如下:
Failed to load http://localhost:8088/test/get.html: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8020' is therefore not allowed access.
發生ajax跨域問題的原因:(三個原因同時滿足才可能產生跨域問題)
(1)瀏覽器限制
發生ajax跨域的問題的時候后端是正常執行的,從后台打印的日志可以看出,而且后台也會正常返回數據。瀏覽器為了安全進行了限制,說白了就是瀏覽器多管閑事。
(2)跨域:
當協議、域名、端口不一致瀏覽器就會認為是跨域問題。
(3)XHR(XMLHttpRequest)請求,也就是ajax請求
如果不是ajax請求,不存在跨域問題(這個我們應該可以理解,瀏覽器直接訪問以及a標簽跳轉等方式都不會產生跨域問題)。
2.解決思路
針對上面三個原因可以對跨域問題進行解決。思路如下:
(1)瀏覽器端:瀏覽器允許跨域請求,這個不太現實,我們不可能改每個客戶端
(2)XHR請求使用JSONP(JSON with Padding)方式進行方式。它允許在服務器端集成Script tags返回至客戶端,通過javascript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式)。
(3)針對跨域問題解決:
被調用方:也就是服務器端接口,服務器允許跨域。但是如果某些情況服務器端不是我們寫的就不可行了。
調用發:也就是JS客戶端,隱藏跨域。通常是通過代理的形式隱藏跨域請求,使請求都類似於同一域下發出a標簽。
3.瀏覽器禁止檢查-從瀏覽器層次解決
比如chrom啟動的時候設置參數關閉安全檢查,如下:
chrome --disable-web-security --user-data-dir=g:/test
設置之后可以正常進行訪問,這也進一步證明了跨域問題與后台無關。
4..采用JSONP解決,針對XHR原因
JSONP(JSON with Padding) 是一種變通的方式解決跨域問題。JSONP是一種非官方的協議,雙方進行約定一個請求的參數。該協議的一個要點就是允許用戶傳遞一個callback參數給服務端,然后服務端返回數據時會將這個callback參數作為函數名來包裹住JSON數據,這樣客戶端就可以隨意定制自己的函數來自動處理返回數據了。
JSONP發出的請求類型是script,不是XHR請求,所以可以繞過瀏覽器的檢查。JSONP返回的是application/javascript,普通的xhr請求返回的是application/json。
JSONP的原理:通過向界面動態的添加script標簽來進行發送請求。script標簽會加上callback參數以及_,_是為了防止請求被緩存。
比如我們發送一個請求地址是http://localhost:8088/test/get.html?name=zhangsan&callback=handleCallback&_=123。后端看到有約定的參數callback,就認為是JSONP請求,如果XHR正常請求的響應是{success: true},那么后端會將回傳的JSON數據作為參數,callback的值作為方法名,如: handleCallback({success: true}), 並將響應頭的Content-Type設為application/javascript,瀏覽器看到是JS響應,則會執行對應的handleCallback(data)方法。
1.JSONP弊端
(1)服務器端代碼需要改動
(2)只支持get方法,由於JSONP原理是通過script標簽實現的,所以只能發送get請求
(3)不是XHR異步請求。所以不能使用XHR的一些特性,比如異步等。
2.測試JSONP
后端:增加一個advice
package cn.qlq.aspect; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice; @ControllerAdvice public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice { public JsonpAdvice() { super("callback"); } }
前端:采用JSON包裝的JSONP請求
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <script type="text/javascript" src="js/jquery-1.8.3.js" ></script> <body> </body> <script> + function test() { $.ajax({ type : "get", async:false, url : "http://localhost:8088/test/get.html?name=zhangsan", dataType : "jsonp",//數據類型為jsonp jsonp: "callback",//服務端用於接收callback調用的function名的參數 success : function(data){ console.log(data); }, error:function(){ alert('fail'); } }); }(); </script> </html>
結果:
(1)請求是script
請求頭:
(2)查看響應數據頭和數據:
數據如下:
/**/jQuery18309128178844464243_1575299406254({"name":"zhangsan","callback":"jQuery18309128178844464243_1575299406254","_":"1575299406287"});
補充:JSONP也可以自己定義返回的方法名稱,默認是JSON生成的隨機字符串
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <script type="text/javascript" src="js/jquery-1.8.3.js" ></script> <body> </body> <script> var handleJSONPresponse = function (res) { console.log(1); console.log(res); console.log(2); } function test() { $.ajax({ type : "get", async:false, url : "http://localhost:8088/test/get.html?name=zhangsan", dataType : "jsonp",//數據類型為jsonp jsonp: "callback",//服務端用於接收callback調用的function名的參數 jsonpCallback: "handleJSONPresponse", // callbacl的value值,不傳由jquery隨機生成 error:function(){ alert('fail'); } }); } test(); </script> </html>
查看請求數據:參數加_是為了防止瀏覽器緩存JS請求
查看響應數據:
結果:
5.跨域解決-被調用方解決(服務端允許跨域)
這里所說的被調用方一般也就是指的是服務端。
1.常見J2EE應用架構
客戶端發送請求到http服務器,通常是nginx/Apache;http服務器判斷是靜態請求還是動態請求,靜態請求就直接響應,動態請求就轉發到應用服務器(Tomcat\weblogic\jetty等)。
當然也有省去中間靜態服務器的應用,就變為客戶端直接請求應用服務器。
2.被調用方解決
被調用方通過請求頭告訴瀏覽器本應用允許跨域調用。可以從tomcat應用服務器響應請求頭,也可以從中間服務器向請求頭添加請求頭。
(1)瀏覽器先執行還是先判斷請求是XHR請求?
查看下面的簡單請求與非簡單請求的解釋。
(2)瀏覽器如何判斷?
分析普通請求和跨域請求的區別:
普通請求的請求頭如下:
XHR的請求如下:
可以看出XHR請求的請求頭會多出一個Origin參數(也就是域),瀏覽器就是根據這個參數進行判斷的,瀏覽器會拿響應頭中允許的。如果不允許就產生跨域問題,會報錯。
補充:關於XHR請求頭中攜帶X-Requested-With與Origin
我自己測試,如果用jquery的ajax訪問自己站內請求是會攜帶X-Requested-With參數、不帶Origin參數,如果訪問跨域請求不會攜帶X-Requested-With參數,會攜帶Origin參數。
if ( !options.crossDomain && !headers["X-Requested-With"] ) { headers["X-Requested-With"] = "XMLHttpRequest"; }
1.被調用方過濾器中實現支持跨域
package cn.qs.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletResponse; /** * 允許跨域請求 */ @WebFilter(filterName = "corsFilter", urlPatterns = "/*") public class CorsFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse response2 = (HttpServletResponse) response; response2.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8020"); response2.setHeader("Access-Control-Allow-Methods", "GET"); chain.doFilter(request, response); } @Override public void destroy() { } @Override public void init(FilterConfig arg0) throws ServletException { } }
上面Access-Control-Allow-Origin是允許跨域請求的域, Access-Control-Allow-Methods 是允許的方法。
我們再次查看XHR請求頭和響應頭:
如果允許所有的域和方法可以用:
response2.setHeader("Access-Control-Allow-Origin", "*");
response2.setHeader("Access-Control-Allow-Methods", "*");
再次查看請求頭和響應頭:
這種跨域是不支持攜帶cookie發送請求的。
2.簡單請求和非簡單請求
簡單請求是先執行后判斷,非簡單請求是先發一個預檢命令,成功之后才會發送請求。
(1)簡單請求:請求的方法為GET\POST\HEAD方法中的一種;請求的header里面無自定義頭,並且Content-Type為:text/plain、multipart/form-data、application/x-www-form-urlencoded中的一種。
只有同時滿足以上兩個條件時,才是簡單請求,否則為非簡單請求
(2)非簡單請求:put、delete方法的ajax請求;發送json格式的ajax請求;帶自定義頭的ajax請求。最常見的是發送json格式的ajax請求。非簡單會發送兩次請求:一個options的預檢請求、預檢請求根據響應頭判斷正確之后發送數據請求。
發送一個非簡單請求:
后端:
package cn.qs.controller; import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.collections.MapUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/test") @RestController public class TestController { @GetMapping("/get") public Map<String, Object> get(@RequestParam Map<String, Object> condition) { if (MapUtils.isEmpty(condition)) { condition = new LinkedHashMap<>(); condition.put("param", null); } return condition; } @PostMapping("/getJSON") public String getJSON(@RequestBody String param) { System.out.println(param); return param; } }
前端
function test() { $.ajax({ url: "http://localhost:8088/test/getJSON.html", type: "POST", data: JSON.stringify({name : "張三"}), contentType: "application/json;charset=utf-8", success: function(res) { console.log(res); } }); } test();
結果:(發送預檢請求的時候報錯)
控制台報錯: (發送預檢的響應頭未設置需要的響應頭)
Failed to load http://localhost:8088/test/getJSON.html: Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.
修改filter
package cn.qs.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletResponse; /** * 允許跨域請求 */ @WebFilter(filterName = "corsFilter", urlPatterns = "/*") public class CorsFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse response2 = (HttpServletResponse) response; // 允許請求的域(協議://IP:port) response2.setHeader("Access-Control-Allow-Origin", "*"); // 允許請求的方法 response2.setHeader("Access-Control-Allow-Methods", "*"); // 正確的響應預檢請求 response2.setHeader("Access-Control-Allow-Headers", "Content-Type"); chain.doFilter(request, response); } @Override public void destroy() { } @Override public void init(FilterConfig arg0) throws ServletException { } }
再次前端發送請求:(響應頭增加Access-Control-Allow-Headers預檢請求會正常響應,預檢成功之后會發送正常的數據請求,所以看到是發出兩個請求)
補充:預檢命令可以緩存,過濾器向響應頭增加如下響應頭:(瀏覽器會緩存1個小時的預檢請求)
// 緩存預檢命令的時長,單位是s response2.setHeader("Access-Control-Max-Age", "3600");
1小時內發送非簡單請求只會預檢請求1次。我們可以用chrom的disable cache 禁掉緩存測試:
3.帶cookie的跨域請求
同域下發送ajax請求默認會攜帶cookie;不同域發送cookie需要進行設置,前后台都需要設置。
(1)首先明白跨域請求需要后台進行設置:請求頭的值 Access-Control-Allow-Origin 不能是*,必須是具體的域。需要根據請求頭的Origin獲取到請求的域之后寫到響應頭中。
(2)響應頭也需要增加允許攜帶cookie的字段 。
// 允許cookie response2.setHeader("Access-Control-Allow-Credentials", "true");
(3)客戶端發送ajax請求的時候需要withCredentials: true 允許攜帶cookie。A發ajax請求給B, 帶着的是B的cookie, 還是受限於同源策略, ajax的Request URL是B, cookie就是B的
先在C:\Windows\System32\drivers\etc\hosts下面增加虛擬域名:
127.0.0.1 a.com
127.0.0.1 b.com
上面a.com 用於訪問靜態頁面,b.com 用於接收后端請求。
后端過濾器修改
package cn.qs.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; /** * 允許跨域請求 */ @WebFilter(filterName = "corsFilter", urlPatterns = "/*") public class CorsFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; // 允許訪問的源 String headerOrigin = request.getHeader("Origin"); if (StringUtils.isNotBlank(headerOrigin)) { response.setHeader("Access-Control-Allow-Origin", headerOrigin); } // 允許訪問的方法 response.setHeader("Access-Control-Allow-Methods", "*"); // 正確的響應預檢請求 response.setHeader("Access-Control-Allow-Headers", "Content-Type"); // 允許預檢命令緩存的時間 response.setHeader("Access-Control-Max-Age", "3600"); // 允許cookie response.setHeader("Access-Control-Allow-Credentials", "true"); chain.doFilter(request, response); } @Override public void destroy() { } @Override public void init(FilterConfig arg0) throws ServletException { } }
后端Controller:
@GetMapping("/getCookie") public String getCookie(@CookieValue(value = "cookie1", required = false) String cookie, HttpServletRequest request) { System.out.println("cookie: " + cookie); System.out.println("Origin: " + request.getHeader("Origin")); return cookie; } @GetMapping("/setCookie") public String setCookie(HttpServletRequest request, HttpServletResponse response) { Cookie cookie2 = new Cookie("cookie1", "value1"); cookie2.setPath("/"); response.addCookie(cookie2); String cookie = "cookie1=value1"; return cookie; }
前端JS:
function test() { $.ajax({ type : "get", async: false, url : "http://b.com:8088/test/getCookie.html", xhrFields: { withCredentials: true }, success: function(res) { console.log("res: " + res); }, error:function(){ alert('fail'); } }); } test();
測試:
(1)如果直接執行前端不會傳cookie,因為沒有cookie。如下:(由於我們訪問的服務是b.com域名,我們的cookie需要是b.com域名下的cookie)
首先我們訪問后台 http://b.com:8088/test/setCookie.html 獲取cookie,當然可以通過document.cookie進行設置
(2)接下來再訪問后台:
請求頭如下:
響應頭如下:
(3)后台控制台日志
cookie: value1
Origin: http://a.com:8020
4.帶自定義頭的跨域請求
過濾器修改,根據自定義請求頭在響應頭中增加允許的請求頭:
package cn.qs.filter; import java.io.IOException; import java.util.Enumeration; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; /** * 允許跨域請求 */ @WebFilter(filterName = "corsFilter", urlPatterns = "/*") public class CorsFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; // 允許訪問的源 String headerOrigin = request.getHeader("Origin"); if (StringUtils.isNotBlank(headerOrigin)) { response.setHeader("Access-Control-Allow-Origin", headerOrigin); } // 允許訪問的方法 response.setHeader("Access-Control-Allow-Methods", "*"); // 正確的響應預檢請求 // response.setHeader("Access-Control-Allow-Headers", "Content-Type"); // 允許自定義的請求頭(根據自定義請求頭) String headers = request.getHeader("Access-Control-Request-Headers"); if (StringUtils.isNotBlank(headers)) { response.addHeader("Access-Control-Allow-Headers", headers); } // 允許預檢命令緩存的時間 response.setHeader("Access-Control-Max-Age", "3600"); // 允許cookie response.setHeader("Access-Control-Allow-Credentials", "true"); chain.doFilter(request, response); } @Override public void destroy() { } @Override public void init(FilterConfig arg0) throws ServletException { } }
Controller:
@GetMapping("/getHeader") public JSONResultUtil<String> getHeader(@RequestHeader("x-header1") String header1, @RequestHeader("x-header2") String header2) { System.out.println(header1 + " " + header2); return new JSONResultUtil(true, header1 + " " + header2); }
前端:
<script> function test() { $.ajax({ url: "http://localhost:8088/test/getHeader.html", type: "get", headers: { "x-header1": "header1" }, beforeSend: function(xhr) { xhr.setRequestHeader("x-header2","header2"); }, xhrFields: { withCredentials: true }, success: function(res) { console.log(res); } }); } test(); </script>
結果:
我們禁調緩存會發送兩條請求:
(1)預檢請求
(2)第二條請求
5. 被調用方解決-nginx解決方案(替代上面的filter的作用)
這里用被調用方nginx解決是通過nginx代理之后增加所需的響應頭。
我們還是基於上面的配置的本地域名。 下面 a.com 用於訪問靜態頁面, b.com 用於接收后端請求。
127.0.0.1 a.com
127.0.0.1 b.com
(1)打開nginx/conf/nginx.conf,在最后的 } 前面增加如下:
include vhost/*.conf;
表示引入 當前目錄/vhost/ 下面所有后綴為conf的文件。
接下來在當前conf目錄創建vhost目錄,並在下面創建b.com.conf文件,內容如下:
server { listen 80; server_name b.com; location /{ proxy_pass http://localhost:8088/; add_header Access-Control-Allow-Methods true; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Max-Age 3600; add_header Access-Control-Allow-Origin $http_origin; add_header Access-Control-Allow-Headers $http_access_control_request_headers; if ($request_method = OPTIONS) { return 200; } } }
注意
(0)前面的是設置監聽域名是b.com、80端口,轉發到 http://localhost:8088/
(1)nginx中請求頭都是小寫,-要用_代替。
(2)$http_origin可以取到請求頭的origin。
(3)最后判斷如果是預檢請求,會直接返回200狀態嗎。
關於nginx的使用:
nginx檢查語法:
E:\nginx\nginx-1.12.2>nginx.exe -t nginx: the configuration file E:\nginx\nginx-1.12.2/conf/nginx.conf syntax is ok nginx: configuration file E:\nginx\nginx-1.12.2/conf/nginx.conf test is successful
nginx重新加載配置文件:
nginx.exe -s reload
重啟和停止
nginx.exe -s reopen
nginx.exe -s stop
注釋掉filter之后修改前台:異步訪問 b.com, 會被請求轉發到: http://localhost:8088/
function test() { $.ajax({ url: "http://b.com/test/getCookie.html", type: "get", headers: { "x-header1": "header1", "x-header3": "header3" }, beforeSend: function(xhr) { xhr.setRequestHeader("x-header2","header2"); }, xhrFields: { withCredentials: true }, success: function(res) { console.log(res); } }); } test();
(1)預檢命令
(2)第二次正式請求
6. Spring注解跨域:@CrossOrigin
加在類上表示所有方法允許跨域,加在方法表示方法中允許跨域。
package cn.qs.controller; import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.collections.MapUtils; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import cn.qs.utils.JSONResultUtil; @RequestMapping("/test") @RestController @CrossOrigin public class TestController { @GetMapping("/get") public Map<String, Object> get(@RequestParam Map<String, Object> condition) { if (MapUtils.isEmpty(condition)) { condition = new LinkedHashMap<>(); condition.put("param", null); } return condition; } @GetMapping("/getCookie") public String getCookie(@CookieValue(value = "cookie1") String cookie) { return cookie; } @PostMapping("/getJSON") public String getJSON(@RequestBody String param) { System.out.println(param); return param; } @GetMapping("/getHeader") public JSONResultUtil<String> getHeader(@RequestHeader("x-header1") String header1, @RequestHeader("x-header2") String header2) { System.out.println(header1 + " " + header2); return new JSONResultUtil(true, header1 + " " + header2); } }
6.調用方解決-隱藏跨域(重要)
被調用方解決跨域是通過nginx代理,將被調用方的請求代理出去,隱藏掉跨域請求。
(1)在nginx/conf/vhost下面新建a.com.conf,內容如下:
server { listen 80; server_name a.com; location /{ proxy_pass http://localhost:8020/; } location /server{ proxy_pass http://b.com:8088/; } }
解釋: 監聽 a.com 的80端口。 默認是/會轉發到本地的8020端口,也就是前台頁面所用的端口;如果是/server/ 開始的會轉發到后端服務所用的路徑。
(2)Controller修改
@GetMapping("/getCookie") public String getCookie(@CookieValue(value = "cookie1", required = false) String cookie, HttpServletRequest request) { System.out.println("cookie1: " + cookie); System.out.println("====================="); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String header = (String) headerNames.nextElement(); String value = request.getHeader(header); System.out.println(header + "\t" + value); } return cookie; }
(3)前端修改:(統一訪問 /server 由nginx轉發到后端服務)
function test() { $.ajax({ url: "/server/test/getCookie.html", type: "get", headers: { "x-header1": "header1", "x-header3": "header3" }, beforeSend: function(xhr) { xhr.setRequestHeader("x-header2","header2"); }, xhrFields: { withCredentials: true }, success: function(res) { console.log(res); } }); } test();
(4)首先設置cookie:(cookie是設置為a.com的cookie,nginx訪問轉發請求的時候也會攜帶到b.com)
查看cookie:
(5)刷新頁面測試:
前端查看:可以看到前端請求發送至 a.com
請求頭:
響應頭:
后端控制台:(可以看到攜帶了x-requested-with參數,仍然是ajax請求,但是相當於同域請求。主機也是b.com(由nginx轉發過來的請求))
cookie1: a.com.cookie
=====================
host b.com:8088
connection close
pragma no-cache
cache-control no-cache
accept */*
x-header3 header3
x-requested-with XMLHttpRequest
x-header2 header2
user-agent Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
x-header1 header1
referer http://a.com/%E6%99%AE%E9%80%9A%E7%9A%84%E6%B5%8B%E8%AF%95/index.html?__hbt=1575599926569
accept-encoding gzip, deflate
accept-language zh-CN,zh;q=0.9
cookie cookie1=a.com.cookie
補充:調用方采用nodejs的express模塊和http-proxy-middleware進行代理
(1)安裝express模塊和http-proxy-middleware模塊:需要以管理員身份運行cmd
cnpm install --save-dev http-proxy-middleware cnpm install --save-dev express
(2)編寫nodejs代理腳本:
const express = require('express'); const proxy = require('http-proxy-middleware'); const app = express(); app.use( '/server', proxy({ target: 'http://b.com:8088', changeOrigin: true, pathRewrite: {'/server' : ''} })); app.use( '/', proxy({ target: 'http://a.com:8020' })); app.listen(80);
注意:上面的順序需要先代理/server,再代理/。否則會先匹配/。
(3)測試方法同上面nginx代理測試。
總結:
0.所謂的跨域請求是指XHR請求發送的時候 協議、域名、端口不完全一致的情況。只要有一個不同就是跨域。
1.如果用jquery的ajax訪問自己站內請求是會攜帶X-Requested-With參數、不帶Origin參數;如果訪問跨域請求不會攜帶X-Requested-With參數,會攜帶Origin參數。
2.后端獲取請求頭的時候不區分大小寫,比如說前端發送的請求頭是 x-header1:header1。后端可以用 request.getHeader("X-HEADER1"); 接收。