解决同源策略限制以实现跨域访问的两个方法:JSONP和CORS
同源的定义:同源是指,域名,协议,端口相同。
比如:在下表中找出与 http://www.aaa.com:8888/abc/ 同源的URL
URL | 结果 | 原因 |
http://www.bbb.com:8888/bbb/ | 不同源 | 域名不一致 |
https://www.aaa.com:8888/ccc/ | 不同源 | 协议不一致 |
http://www.aaa.com:8080/ddd/ | 不同源 | 端口不一致 |
http://www.aaa.com:8888/eee/ | 同源 |
同源策略是浏览器的一种安全机制,不同源的客户端脚本在没有明确授权的情况下,不能读写其他源的资源,例如:在http://www.aaa.com下的js脚本不能通过ajax去访问http://www.bbb.com下的资源。设想一下,如果没有同源策略的机制,那么黑客就很容易通过脚本来攻击目标的网站。
由于同源策略的存在,所以导致了跨域的问题。我们在写项目的时候常常会需要解决跨域问题,希望去访问其他站点的资源,这里简单的介绍两种实现跨域访问的方法:JSONP,CORS。
JSONP
JSONP是一种将JSON传递给浏览器的方式,JSONP在JSON的数据基础上进行了JavaScript的封装,使它看起来像一个JavaScript的函数,比如:callback({"name": "Messi", "age": 12})。为什么要返回这种格式呢?因为我们知道script标签不属于同源策略限制的范畴,所以我们可以使用script标签的src属性来访问目标地址,这时就会返回一个如上面那个数据格式一样的数据:一个js函数,而我们真正想要的却是这个函数中的参数(JSON数据),这时就可以实现一个callback函数,将返回的JSON数据进行处理(比如渲染到页面中)
前端页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.js"></script>
</head>
<body>
</body>
<script>
function handler(data) {
console.log(data)
}
</script>
<script src="http://127.0.0.1:8888/students_jsonp?callback=handler"></script>
</html>
可以看到该页面加载时就会请求http://127.0.0.1:8888/students_jsonp这个地址,后面的参数callback表示告诉服务器我需要返回一个由handler函数包裹的数据,因为在该页面中有一个handler函数可以处理得到的数据。我这里直接将数据打印到控制台。
在后端进行封装(后端使用的是Flask):
@app.route('/students_jsonp/')
def students_jsonp():
callback = request.args.get('callback')
return Response(callback + '(' + json.dumps(students) + ')')
当我加载页面时,就在控制台得到了想要的数据:
但是JSONP存在很多问题,比如只能使用get方法,不能自定义http首部等,而且由于JSONP其实是绕过同源策略的限制,所以存在安全隐患,容易被黑客利用。
CORS
CORS,即:跨域资源共享(Cross-Origin Resource Sharing),使用CORS后,如果访问来自不同的源,就可以只允许来自特定的源的访问,CORS相比与JSONP而言更加安全。
客户端在进行跨域访问源A的时候,会携带一个名为Origin的请求参数,该参数的值是当前客户端所在的源B,源A所在服务端会事先保存有一个允许访问的源的清单,当服务端接收到来自源B的请求时,会判断源B是否在清单中,如果在清单中就允许源B的访问,并在响应头的Access-Control-Allow-Origin这个参数中放入与请求头中Origin参数一样的值,否则不允许访问,并返回403错误。另外,如果是允许所有的源访问,则将Access-Control-Allow-Origin的值设置为 * 。
使用了CORS我们就可以用ajax来实现跨域访问了。
源B下的页面代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.js"></script>
</head>
<body>
</body>
<script>
$.ajax({
url: "http://127.0.0.1:8888/students_cors/", //请求的服务端地址
type: "get",
dataType: "json",
success: function (data) {
//成功之后的处理,返回的数据就是 data
console.log(data)
},
error: function () {
console.log('error'); //错误的处理
}
});
</script>
</html>
这里是实现了页面加载就发出ajax请求,去请求另一个源的数据,将接收到数据打印在控制台中。
我们再来看看源A后端接口的实现(使用的是Flask):
allow_origin = ['http://127.0.0.1:9999']
@app.route('/students_cors/')
def students_cors():
origin = request.headers.get('Origin')
if origin in allow_origin: # 判断源B是否在允许访问的清单中
resp = Response(json.dumps(students), content_type='application/json')
resp.headers['Access-Control-Allow-Origin'] = origin
return resp
else:
return Response(status=403) # 返回403错误信息
当我们访问页面时,在浏览器的控制台中成功打印出了获取的数据:
再查看请求头和响应头:
这里只是实现CORS简单的用法,其实还有很多其他的操作,比如还可以限制请求的方法(get, post等),可以实现用户认证等等。
因此,不管是从功能性还是安全性来说,都更加推荐使用CORS。