什么是跨域
「浏览器的同源策略」 导致的。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。浏览器引入同源策略主要是为了防止XSS,CSRF攻击。 我们来看几个例子,理解一下跨域
「当前页面url」 「被请求页面url」 「是否跨域」 「原因」 http://www.test.com/ http://www.test.com/index.html 否 同源(协议、域名、端口号相同) http://www.test.com/ https://www.test.com/index.html 跨域 协议不同(http/https) http://www.test.com/ http://www.baidu.com/ 跨域 主域名不同(test/baidu) http://www.test.com/ http://blog.test.com/ 跨域 子域名不同(www/blog) http://www.test.com:8080/ http://www.test.com:7001/ 跨域 端口号不同(8080/7001)
小结:不同端口,不同协议,不同域名,主域相同,子域不同,都会触发跨域问题,不允许通信
为什么要跨域
- 从其他域获取自己想要的资源
如何解决:本文主要介绍JSONP,CORS,和反向代理这三个常用方法
jsonp方法:
只支持get请求,但是兼容性好,并且简单实用,核心思想是网页通过添加一个script元素,向服务器请求JSON数据,将数据放在一个指定名字的回调函数的参数位子传过来
- 看例子前,先看这里,原理是请求文件中先定义一个回调函数,然后通过script传到目标页面,在目标页面中触发回调,把要获取的值作为回调函数的参数,然后原页面那个回调函数的参数就是要获取的数据了,拿出来就可以了
- 请求跨域的文件
//这里通过script不受同源政策的影响的原理向目标页面请求一个callback回调函数
<script src="http://test.com/data.php?callback=dosomething"></script>
// 向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
// 处理服务器返回回调函数的数据
<script type="text/javascript">
//在目标页面回调函数触发后,这里的参数就是送过来的数据
function dosomething(res){
// 处理获得的数据
console.log(res.data)
}
- 被跨域的文件
//通过设置content-type能够声名返回的内容类型
header('Content-type:application/json');
$dosomething-htmlspecialchars($_GET['callback'])
//这里获取要传递的数据
$data='我是被传递的数据'
//这里触发回调函数,并且把要传递的数据作为参数传递,这时候原页面的函数的参数就是这里的data
echo $dosomething,'("',$data'")';
小结:JSONP方法解决跨域,主要就是利用script标签不受同源政策影响,然后向指定页面传递回调函数,再把需求的数据当成函数参数返回到主页面,然后解析参数,优点:兼容性高,操作简单,缺点:只能应用于get请求
CORS方法: 全称是"跨域资源共享
- CORS是一个新的W3C标准,它新增的一组「http首部字段」允许服务器声明那些来源有权限访问哪些资源,通俗的说就是,设置好了指纹解锁,来了跨域请求,根据配置的首部信息,看你符不符合要求
- 会在http首部增加Access-Control-Allow-Origin的配置信息,从而判断哪些资源站可以进行跨域请求,
- Cors分为两类,一类是简单请求,一类是非简单请求
条件:
1、请求方式:HEAD、GET、POST
2、请求头信息:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type 对应的值是以下三个中的任意一个
application/x-www-form-urlencoded
multipart/form-data
text/plain
注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求
小结:简单请求,一次请求,复杂请求,会请求两次,在发送数据前会发送一次请求做预检,只有预检通过了,才会再发一次请求做数据传输
简单请求
- 我们先来看一个简单请求:请求页面端口是8001,接受请求页面端口是8000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<button id="a" class="btn btn-success">go</button>
<script>
//这是一个jquery的ajax请求,点击向http://127.0.0.1:8000/time/发送一个get请求,
$("#a").click(function () {
$.ajax({
url:'http://127.0.0.1:8000/time/',
type:'get',
success:function (data) {
alert(data)
}
}
)
})
</script>
</body>
</html>
服务端:
def get_time(request):
//判断是否为get请求
if request.method == 'GET':
ntime = time.strftime('%Y-%m-%d %X')
obj = HttpResponse(ntime)
//设置请求规则,必须是http://127.0.0.1:8001发送来的请求,才会返回
obj['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8001'
return obj
复杂请求
复杂请求:预检的请求方式OPTIONS,如果复杂请求是put等请求,服务端要设置允许某请求,否则预检不通过,在Access-Control-Request-Method设置,如果设置了请求头,则需要设置允许请求头,否则预检不通过,我们来看一个案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<button id="a" class="btn btn-success">go</button>
<script>
$("#a").click(function () {
$.ajax({
url:'http://127.0.0.1:8000/money/',
//请求方式
type:'post',
contentType:'application/json',
data:{"name":"yjh"},
success:function (data) {
alert(data)
}
}
)
})
</script>
</body>
</html>
服务端文件
from django.http import QueryDict
def get_money(request):
# print(request.body)
if not request.body:
obj = HttpResponse('1000000000000')
else:
data = request.body # post请求传过来的数据以二进制的形式存放于request.body
# 将request.body的二进制数据转化为字典形式
dic = QueryDict(data.decode('utf-8'),encoding='utf-8')
name = dic.get('name')
obj = HttpResponse(name)
//判断是否是options请求
if request.method == 'OPTIONS':
//请求头是否为content-type
obj['Access-Control-Allow-Headers'] = 'Content-Type'
//接口是否为8001端口
obj['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8001'
return obj
小结:正如上文所说,当复杂请求时候,会发送options请求进行预检,并且会检测请求头设置,
总结:cros解决跨域是一个较为好的方法,适用性强,而且支持广泛,缺点就是 复杂请求的时候得先做一个预检,再发真实的请求,发了两次请求会有性能上的损耗 ,默认不支持cookie
反向代理(重点!!!)
- 利用反向代理解决跨域,是现在的主流方法因为很多后端接口也是不会在公网开放的,这时候只能nginx代理
- 既然不能跨域请求,那我们就不跨域,在请求到达服务前部署一个服务,将接口进行转发,这就是反向代理,通过一定的转发可以将前端请求转发到其他的服务,
- 通俗的讲:nginx 反向代理就是。比如你是男生,要送999朵玫瑰给你喜欢的女孩子,然后女生宿舍规定男生不能进,所以你去到宿舍楼下就要找宿管阿姨帮你代理一下这个送花的骚操作。在女生内部没有跨域(跨性别)的说法,而前端相对服务端来说就有,防止女孩受到男孩子的攻击(亦总生动举例)
我们来看一段Nginx代码:
server {
listen 80;
server_name localhost;
## 用户访问 localhost,则反向代理到https://api.shanbay.com
location / {
root html;
index index.html index.htm;
proxy_pass https://api.shanbay.com;
}
}
小结:就是访问localhost会去到api.shanbay.com
- 一般的情况下,我们的HTML文件时放置在Nginx服务器上面的,即通过输入 http://localhost/index.html ,但是在前端进行调试的时候,我们可能是通过 使用 file:///E:/nginx/html/index.html 来打开HTML。服务器打开不是特别方便 ,我们之所以要部署在服务器上,是想要使用浏览器自带的CORS头来解决跨域问题,如果不想把HTML放置在Nginx中,而想通过本地打开的方式来调试HTML,可以通过自己添加
Access-Control-Allow-Origin等http头,但是我们的AJAX请求一定要加上http://127.0.0.1/request,而不能直接是/request,于是将nginx.conf作如下配置
location / {
root html;
index index.html index.htm;
# 配置html以文件方式打开
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' *;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' *;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
# 代理到8080端口
proxy_pass http://127.0.0.1:8080;
}
Cookie
- 如果Cookie的域名部分与当前页面的域名不匹配就无法写入。所以如果请求 www.a.com ,服务器 proxy_pass 到 www.b.com 域名,然后 www.b.com 输出
domian=b.com的 Cookie,前端的页面依然停留在 www.a.com 上,于是浏览器就无法将 Cookie 写入
location / {
# 页面地址是a.com,但是要用b.com的cookie
proxy_cookie_domain b.com a.com; #注意别写错位置了 proxy_cookie_path / /;
proxy_pass http://b.com;
}
总结:使用反向代理就是 通过Nginx反向代理将对真实服务器的请求转移到本机服务器来避免浏览器的"同源策略限制