一、 什么是跨域
前台调用后台接口时如果不在同一个域就会出现跨域问题,这里的域包括端口和域名。
二、跨域问题出现的条件
1、浏览器限制
浏览器发送请求时会进行安全校验 ,如果发现是跨域的就会报跨域安全问题。跨域问题出现的的原因与后台接口无关,是浏览器出于安全考虑所做的限制。
2、必须是XML(XmlHttpRequest)请求
那么怎么查看请求类型呢 ?
打开chrome调试工具,切换到network ,type就是请求类型
3、协议、域名、端口任何一个不同会产生跨域问题
下面是端口不同导致的跨域问题,直观一点浏览器端出现’access-control-allow-origin’字眼时 恭喜你碰到了跨域问题
三、解决思路
重点来来了,针对跨域问题出现的原因逐个击破
1、基于浏览器限制
设置浏览器,让其不进行安全校验。不同的浏览器设置方法不同 ,因为实用性不大,在这里不做介绍。
2、基于XHR
改变请求类型解决跨域问题是使用jsonp。jsonp是json的补充使用方式,不是官方协议,只支持get请求。
ajax请求案例如下
$.ajax({
url:base+'/test',
dataType:'jsonp',
jsonp:'callback'//默认为callback ,可定义。
success:function(json) {
}
})
jsonp解决需要改动后台代码,当不改动时会出现语法错误,例如
uncaught syntaxError:unexpected token:。
c#解决方案参考stackoverflow上面第一条评论
https://stackoverflow.com/questions/9421312/jsonp-with-asp-net-web-api/18206518#18206518
jsonp原理:请求类型发生变化,由xhr变成了script。jsonp请求是通过
动态创建script标签, 在script中将请求发出去 ,发送完成后自动销毁。
返回结果类型发生变化 ,由json变成js脚本,也就是Content-Type由application/json变成了application/javascript。
jsonp发生请求时会带上callback参数
callback是jsonp的约定 可以通过jsonp:"callback1"自定义指定,后台也应该相应改变。
打开Chrome查看jsonp请求,除了callback参数外还有一串数字,作用是防止请求被缓存,若需要缓存可以添加cache:true。
3、基于跨域
3.1 被调用方解决,后台接口做修改,支持跨域。
当发生跨域请求时请求头会多出一个origin字段(当前域信息字段)
如果是不带cookie的请求,在返回头中添加相对应的信息即可
c# 代码
Response.Headers["Access-Control-Allow-Origin"]= "http://localhost:7329";
Response.Headers["Access-Control-Allow-Methods"] = "GET";
返回头会增加如下信息
如果要容许所有的域和方法用*(通配符)代替
用通配符代替不是满足所有需求,下面我们先来弄明白什么是简单请求和非简单请求。
在发送http请求时,浏览会判断一个请求是简单请求还是非简单请求,如果是简单请求,就会先执行后判断是否跨域,如果是非简单请求,浏览器首先会发送一个预检命令 ,预检命令通过后再把请求发送出去。
简单请求的定义
方法包括 post、get。 head,请求header里面无自定义请求头
content-Type为以下几种:
text/plain
multipart/form-data
application/x-www-form-url
常见的非简单请求
put,delete方法的ajax请求,发送json格式的ajax请求,带自定义头的ajax请求。
下面我们来验证一个非简单请求
$.ajax({
url: "http://192.168.1.133:8054/demo/test/Index",
dataType: 'json',
contentType:'application/json;charset=utf-8',
success: function (json) {
let result = json;
console.log(result);
},
error: function (res,type,err) {
console.log(res,type,err)
}
})
当后端代码加了header为:
Response.Headers["Access-Control-Allow-Origin"] = "*";
Response.Headers["Access-Control-Allow-Methods"] = "*";
浏览器报错:Failed to load http://192.168.1.133:8054/demo/test/Index: Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.
此时是预检命令报错,浏览器只发送了一次预检请求,类型为options
上面错误信息说明Content-Type没有被容许,我们后端代码变为
Response.Headers["Access-Control-Allow-Origin"] = "*";
Response.Headers["Access-Control-Allow-Methods"] = "*";
Response.Headers["Access-Control-Allow-Headers"] = "Content-Type";
时请求成功,非简单请求会请求两次 ,预检命令通过后才会发真正的请求
预检命令会增加服务器负担 预检命令可以缓存
设置代码
Response.Headers["Access-Control-Max-Age"] = "3600";
缓存不代表不发送预检请求,而是预检命令缓存在浏览器。浏览器disable cache不勾选的情况下,只要请求过一次,当后端把Response.Headers[“Access-Control-Allow-Headers”] = “Content-Type”; 去掉时,也是只发送一次请求而且是成功的。
当请求带cook时
$.ajax({
url: "http://192.168.1.133:8054/demo/test/Index",
dataType: 'json',
xhrFields:{
withCredentials:true
},
success: function (json) {
let result = json;
console.log(result);
}
})
cookie是被调用方的cookie,因为所读的cookie只能是本域的。
当带cookie请求时会报错:Failed to load http://192.168.1.133:8054/demo/test/Index: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard '’ when the request’s credentials mode is ‘include’. Origin ‘http://localhost:7329’ is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
意思是请求带cookie时不能用这个通配符来代替域名。当我们把通配符换成固定域名后又报错: Failed to load http://192.168.1.133:8054/demo/test/Index: The value of the ‘Access-Control-Allow-Credentials’ header in the response is ‘’ which must be ‘true’ when the request’s credentials mode is ‘include’. Origin ‘http://localhost:7329’ is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
查看错误信息后,后端接口加一个条件
Response.Headers["Access-Control-Allow-Credentials"] = "true";
请求成功
要有其他域名来调用呢,请求的域名可以从请求头的Origin字段来获取
var dns = Request.Headers["Origin"];
Response.Headers["Access-Control-Allow-Origin"] = dns;
带自定义头的请求
$.ajax({
url: "http://192.168.1.133:8054/demo/test/Index",
dataType: 'json',
headers: {
"x-header1":"AAA"
},
beforeSend: function (xhr) {
xhr.setRequestHeader("x-header2","BBB")
},
success: function (json) {
let result = json;
console.log(result);
}
})
请求后报错:Failed to load http://192.168.1.133:8054/demo/test/Index: Request header field x-header2 is not allowed by Access-Control-Allow-Headers in preflight response.意思是返回头中没有header1和header2的信息。加上两个header后请求成功。
Response.Headers["Access-Control-Allow-Headers"] = "x-header1,x-header2";
header写死也是不理想的 可以获取请求头中的header赋值。
var headers = Request.Headers["Access-Control-Request-Headers"];
if(headers!=null)
Response.Headers["Access-Control-Allow-Headers"] = headers;
总结一下
//支持所有自定义请求头
var headers = Request.Headers["Access-Control-Request-Headers"];
if(headers!=null)
Response.Headers["Access-Control-Allow-Headers"] = headers;
Response.Headers["Access-Control-Allow-Methods"] = "*";
//设置预检命令缓存周期
Response.Headers["Access-Control-Max-Age"] = "3600";
//支持cookie
Response.Headers["Access-Control-Allow-Credentials"] = "true";
上述解决方案是跨域方的浏览器直接发送请求到被调用方的应用服务器,示例图:
实际应用中都是调用方浏览器先发送请求到被调用方的http服务器,http服务器再把请求转到应用服务器,应用服务器处理之后原路返回。
在这个过程中有两个地方可以增加响应头,http服务器和应用服务器,在应用服务器方添加响应头上面已经介绍过了 ,下面我们来说一说在http服务器(nginx)中添加响应头。
虚拟主机的概念:多个域名指向同一个服务器,服务器根据不同的域名转到不同的应用服务器。
被调用方虚拟主机的配置
打开服务器host文件, windows10 下host的路径为:C:\Windows\System32\drivers\etc
在host文件末尾添加127.0.0.1 b.com ,映射一个本地域名。
host文件无法保存请看这里:https://www.cnblogs.com/lwh-note/p/9005953.html
添加成功后b.com这个域名就是对应127.0.0.1这个ip地址。
然后在nginx.conf末尾添加(大括号内)
include vhost/*.conf //包涵vhost内所有.conf文件
在主目录创建vhost ,vhost下创建 cq.com.conf文件内容为
server{
listen 80;//监听端口
server_name b.com;//监听的域名
location /{
proxy_pass http://localhost:8080/;被调用方端口
}
}
此配置意思是把所有的请求都转到8080。
nginx.exe -t 测试
start nginx.exe 启动
在 b.com.conf增加响应头
server{
listen 80;//监听端口
server_name cq.com;//监听的域名
location /{
proxy_pass http://localhost:8080/;被调用方端口
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Allow-Credentials true;
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;
}
}
}
nginx.exe -t 检测
nginx.exe -s reload 重新载入
nginx.exe -s stop
意思是把请求全部转到b.com
3.2 调用方解决,隐藏跨域。
通过调用方的http服务器的反向代理转发
反向代理 :访问同一个域名的两个不同的url 最终会去到不同的服务器
nginx配置
在本机增加 a.com,a.com表示调用方的虚拟主机
增加配置a.com.conf
server{
listen 80;//监听端口
server_name a.com;//监听的域名
location /{
proxy_pass http://localhost:8081/;调用方
}
location /ajaxserver{
proxy_pass http://localhost:8080/;被调用方
}
}
请求接口全部换成/ajaxserve