跨域问题简单的说就是前台请求一个后台链接,发送请求的前台与后台的地址不在同一个域下,就会产生跨域问题。这里所指的域包括协议、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服务器判断是静态请求还是动态请求,静态请求就直接响应,动态请求就转发到应用服务器(Tomcatweblogicjetty等)。
当然也有省去中间静态服务器的应用,就变为客户端直接请求应用服务器。
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)简单请求:请求的方法为GETPOSTHEAD方法中的一种;请求的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:WindowsSystem32driversetchosts下面增加虚拟域名:
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: ginx ginx-1.12.2>nginx.exe -t nginx: the configuration file E: ginx ginx-1.12.2/conf/nginx.conf syntax is ok nginx: configuration file E: ginx ginx-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 + " " + 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"); 接收。