0%

浏览器跨域的几种实现

前言

同源策略是浏览器安全策略的核心,非同源不得交互,但是有时候一家公司有自己的不同业务,不同的子域名,或者说有合作方,不同的域名端口需要交互数据呢。这就是跨域的需求。

跨域目前一般常见两种方式:Jsonp、CORS、代理。

JSONP

js在script标签中是不受同源策略限制的,我们也经常能看到<script src="http://xxx.xxx.xx.x/xxx.js">,当然img也不受限制,但是img不太好做后续数据的处理。

jsonp的核心是在前端动态创建一个script标签。并设置src属性指向跨域的服务器。服务器返回包含json数据的javascript函数。前端接受到对应函数和对应的数据,直接执行处理就拿到了我们想调用接口拿到的数据。

demo

我们实现一个demo,后端用我比较熟悉的Flask

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask, request, render_template_string

app = Flask(__name__)

# 假设这是一个返回JSONP响应的服务端点
@app.route('/jsonp')
def jsonp_demo():
# 获得客户端传递的回调函数名称
callback = request.args.get('callback', 'callbackFunction')
data = {'user': 'Alice', 'id': 123} # 这里是我们想要传递给客户端的数据
response = f"{callback}({data})"
return response

if __name__ == '__main__':
app.run(debug=True)

如果有浏览器访问/jsonp接口就会获取回调函数的名称,然后把这个接口需要传递的数据封装进去。然后前端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html>
<head>
<title>JSONP Demo</title>
<script>
// 定义处理JSONP响应的回调函数
function handleJSONPResponse(data) {
console.log('Received data:', data);
}
</script>
<script>
// 动态添加<script>标签进行跨域请求
function requestJSONP(url, callbackName) {
var scriptTag = document.createElement('script');
scriptTag.src = url + '?callback=' + callbackName; // 添加callback参数到URL
document.head.appendChild(scriptTag);
}
// 调用JSONP请求
// 注意更改URL中的端口号为你的Flask服务器运行的端口号
requestJSONP('http://localhost:5000/jsonp', 'handleJSONPResponse');
</script>
</head>
<body>
<h1>JSONP跨域请求示例</h1>
</body>
</html>

前端请求http://localhost:5000/jsonp接口,并获取了数据打印。如果不使用jsonp,那么直接在当前域下请求别的域的接口是不合法的。所以jsonp解决了跨域问题。

优点:简单高效兼容性好

缺点:只支持GET请求,还有xss漏洞的奉献。(src的低危的主要贡献漏洞😂)

CORS跨域

有些人看完上面的jsonp跨域可能想,为啥不能script标签里面执行AJAX的动态请求呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"><title>AJAX Request to Baidu</title>
<script>
document.addEventListener('DOMContentLoaded', function() {
var xhr = new XMLHttpRequest(); // 创建XMLHttpRequest对象
// 配置请求方法、URL以及异步处理
xhr.open('GET', 'http://www.baidu.com', true);
// 设置状态变化回调处理请求结果
xhr.onreadystatechange = function() {
// 仅当请求完成且响应就绪时才运行
if (xhr.readyState === 4) {
// 检查HTTP响应状态码是否为200 OK
if (xhr.status === 200) {
// 这里可以获取到返回内容进行处理,例如打印到控制台
console.log(xhr.responseText);
} else {
// 请求失败处理
console.error('AJAX Request to Baidu failed');
}
}
};
// 发送HTTP请求
xhr.send();
});
</script>
</head>
<body>
</body>
</html>
image-20240313105201430

可以看到仍然被同源策略限制。

怎么解决呢?这里就是CORS了。

1、当浏览器发起跨域请求,它会先发送一个“预检(Preflight)”请求,该请求采用OPTIONS方法,并携带诸如OriginAccess-Control-Request-MethodAccess-Control-Request-Headers之类的特定头信息,询问服务器是否愿意接受跨源请求。

2、服务器的回应是关键所在。如果服务器准许,它会回复一个包含Access-Control-Allow-OriginAccess-Control-Allow-Methods等头信息的“预检响应”,明确告诉浏览器哪些跨域行为是被许可的。

3、一旦服务器的“预检响应”亮绿灯,浏览器随即会发送正式的请求。服务器此时通过检查请求头的Origin字段,决定是否允许访问资源,并在响应头中包含Access-Control-Allow-Origin字段,列明哪些域名可以接收响应数据。

CORS不仅仅是关于XMLHttpRequest请求的,它还涵盖了其他安全机制,包括对cookies和认证的处理。大多数现代浏览器都已支持CORS,但一些旧版的浏览器,像是IE10及其以下版本,则不支持这项功能。

代理实现跨域

我们可以实现一个代理服务器来实现跨域,也就是A和B域之间有一个代理服务器。平常我们写python代码用requets请求百度,从来没有人拦截我们对吧,因为我们不是在浏览器操作,那我们可以请求一个我们自身的接口,这个接口从代码层再去另一个服务器拿数据再返回给前端,那这个接口就是一个代理。

通过实现代理来实现跨域也是一种方法,但是好像不太多见。

服务端代理代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const express = require('express');
const fetch = require('node-fetch');

const app = express();
const PORT = 3000; // 代理服务器端口

// 允许跨域
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});

// 代理路由,将请求转发到百度
app.get('/proxy', async (req, res) => {
try {
const response = await fetch('http://www.baidu.com');
const data = await response.text();
res.send(data); // 将从百度获取的数据发送回客户端
} catch (error) {
console.error('Error making proxy request:', error);
res.status(500).send('An error occurred while making proxy request');
}
});

app.listen(PORT, () => {
console.log(`Proxy server is running on http://localhost:${PORT}`);
});

前端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AJAX Proxy Demo</title>
<script>
function makeRequest() {
var xhr = new XMLHttpRequest();

// 使用自己的代理服务器
xhr.open('GET', 'http://localhost:3000/proxy', true);

xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// 这里将显示百度首页的HTML
console.log(xhr.responseText);
document.getElementById('response').innerHTML = xhr.responseText;
} else {
console.error('Proxy request failed.');
}
}
};

xhr.send();
}
</script>
</head>
<body>
<h1>AJAX Proxy Demo</h1>
<button onclick="makeRequest()">Request Baidu through Proxy</button>
<div id="response"></div>
</body>
</html>

备注

不太明白一些面试总是会问的特别细,比如一些CORS字段什么的,CSP的一些字段等等,感觉太多啦 有时候确实记不住啊,看见知道是什么意思还不行吗?有点废脑子,有时候知道已经理解啦,但是还是记不住那些参数。


采用署名-非商业性使用-相同方式共享 4.0(CC BY-NC-SA 4.0)许可协议
「分享也是一种学习」