什么是同源与跨域?
源 = 协议(http/https)+域名+端口;
同源策略的目的是为了防止恶意网站通过脚本访问其他网站的敏感数据
同源 指的是两个URL的协议 、域名 和端口 完全相同。如果其中任何一个部分不同,则这两个URL被认为是不同源的。
跨域 指的是当一个资源尝试访问与其不同源的资源时,浏览器会阻止这种访问,除非目标资源明确允许跨域访问(比如列入白名单)。跨域问题通常在前端开发中遇到,尤其是在使用AJAX请求或嵌入第三方资源时。
总结来说:两个源相同,称为同源;两个源不同,称为跨域
比如:
场景描述 | 示例 |
---|---|
不同协议 | http://example.com 访问 https://example.com |
不同域名 | https://example.com 访问 https://another.com |
不同子域名 | https://sub.example.com 访问 https://example.com |
不同端口 | http://example.com:80 访问 http://example.com:8080 |
跨域AJAX请求 | https://example.com 请求 https://api.another.com/data |
嵌入第三方资源 | https://example.com 嵌入 https://cdn.another.com/script.js |
问:如果没有跨域限制会发生什么?
如果没有跨域限制,前端代码可以随意访问其他域的资源,这将导致严重的安全问题。例如,恶意脚本可以通过修改DOM来窃取用户的敏感信息,或者伪造请求来执行未经授权的操作。
问:那如何处理跨域问题?
为了安全地处理跨域请求,现代浏览器提供了几种机制:
- CORS(跨域资源共享) :服务器可以通过设置HTTP头来允许特定的跨域请求。
- JSONP :通过动态创建
<script>
标签来加载跨域数据,但只支持GET请求。 - 代理服务器:通过服务器端转发请求来避免跨域问题。
- WebSocket:WebSocket协议不受同源策略限制,可以用于跨域通信。
跨域解决方案
方案1-JSONP
为了更好地理解JSONP的工作原理,我们可以通过模拟后端数据和API接口来实现一个简单的JSONP示例。
JSONP的基本原理
JSONP利用了
<script>
标签不受同源策略限制的特性。前端动态创建一个<script>
标签,并将其src
属性指向目标URL,同时附带一个回调函数名。后端返回的数据会被包裹在这个回调函数中,前端通过定义这个回调函数来处理数据。
- 后端 以下是一个使用Node.js创建的简单HTTP服务器,它模拟了一个返回JSON数据的API接口。注意,后端返回的数据被包裹在一个回调函数中,这是JSONP的核心机制。
js
体验AI代码助手
代码解读
复制代码
// http 服务启动 commonjs 模块化,node早期, es6 模块化 import
const http = require('http');
const url = require('url');
const data = [
{
name: '张三',
age: 25,
city: '北京'
},
{
name: '李四',
age: 25,
city: '北京'
},
]
const server = http.createServer((req,res) => {
res.end("callback("+JSON.stringify(data)+")");
})
server.listen(3000,() => {
console.log('服务启动');
});
在这个示例中,后端返回的数据被包裹在一个名为callback
的函数调用中。前端可以通过定义这个回调函数来处理返回的数据。
在前端,如果我们直接使用fetch
来请求这个API,会因为同源策略的限制而失败。为了解决这个问题,我们可以使用JSONP。
以下是一个简单的JSONP实现:
html
体验AI代码助手
代码解读
复制代码
<div id="list"></div>
<script>
// 定义回调函数
function handleResponse(data) {
const container = document.getElementById('list');
container.innerHTML = `
<p>姓名: ${data[0].name}</p>
<p>年龄: ${data[0].age}</p>
<p>城市: ${data[0].city}</p>
`;
}
// 回调执行
function callback(data) {
handleResponse(data)
}
</script>
<script src="http://localhost:3000/"></script>
在这个示例中,我们定义了一个名为callback
的函数,并将其作为全局函数。后端返回的数据会被这个函数处理并显示在页面上。
封装JSONP
为了更方便地使用JSONP,我们可以将其封装成一个函数:
html
体验AI代码助手
代码解读
复制代码
<div id="list2"></div>
<script>
// 定义处理响应的函数
function handleResponse(data) {
const container = document.getElementById('list2');
container.innerHTML = `
<p>姓名: ${data[0].name}</p>
<p>年龄: ${data[0].age}</p>
<p>城市: ${data[0].city}</p>
`;
}
// 封装JSONP函数
let jsonp = function(url, callback) {
// 1. 创建script标签
let script = document.createElement('script')
// 2. 给script标签添加src属性
script.src = url
// 3. 给script标签添加回调函数
window.callback = callback
// 4. 将script标签添加到页面中
document.body.appendChild(script)
}
// 调用JSONP函数
jsonp('http://localhost:3000/', handleResponse)
</script>
在这个封装中,我们创建了一个jsonp
函数,它接受两个参数:目标URL和回调函数。函数内部动态创建了一个<script>
标签,并将其src
属性指向目标URL。同时,我们将回调函数赋值给window.callback
,以便后端返回的数据能够被正确处理。
JSONP很明显的缺点是只能支持GET请求
方案2-CORS
CORS
是浏览器与服务器直接对接。如果浏览器需要服务器资源,需要得到服务器的允许。
要知道,服务器为了处理多个请求,使用高并发处理机制。但是一个请求可以附带很多信息,对其服务器的影响程度是不一样的。比如有的为了运行页面跳转时页面更新,有点只是拉取文章内容
针对不同请求,CORS规定三种不同的交互方式,分别是:
- 简单请求
- 需要预检的请求
- 附带身份凭证的请求
这三种模式从上到下层层递进,请求可以做的事越来越多,要求也越来越严格。
下面分别说明三种请求模式的具体规范。
3.1 简单请求(Simple Request)
满足以下条件的请求被认为是简单请求:
-
请求方法是
GET
、POST
或HEAD
。 -
请求头仅包含以下字段:
Accept
Accept-Language
Content-Language
Content-Type
(仅限于application/x-www-form-urlencoded
、multipart/form-data
、text/plain
)
对于简单请求,浏览器会直接发送请求,并在请求头中添加 Origin
字段,表示请求的来源。服务器需要返回以下响应头: 如果以上三个条件同时满足,浏览器判定为简单请求。
下面是一些例子:
sql
体验AI代码助手
代码解读
复制代码
// 简单请求 fetch('http:s', { method: 'post', }); // content-type不满足要求,不是简单请求 fetch('http://cros', { method: 'post', headers: { 'content-type': 'application/json', }, });
...
3.2 预检请求(Preflight Request)
不满足简单请求条件的请求会触发预检请求。浏览器会先发送一个 OPTIONS
请求,询问服务器是否允许实际请求。
-
预检请求头:
makefile体验AI代码助手 代码解读 复制代码 Origin: https://example.com Access-Control-Request-Method: POST Access-Control-Request-Headers: Content-Type, Authorization
-
服务器响应头:
makefile体验AI代码助手 代码解读 复制代码 Access-Control-Allow-Origin: https://example.com Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Max-Age: 86400 // 预检请求缓存时间(秒)
如果服务器允许,浏览器会继续发送实际请求。