前言
思考
你是否每次面试前都要临时抱佛脚,重温一遍跨域的知识?
你看了那么多的跨域文章,为什么还要花时间反复的学习这个知识点?
你真的是记性不好,学完就忘了吗?
我的观点
记性并非是阻碍我们学习的主要因素,真正的原因可能在于你的学习方法有待改进。
在学习过程中,我们往往容易满足于表面的“看懂”,认为只要把文字或公式读懂了,就是真正掌握了这个知识点。但实际上,这种“看懂”只是一种浅显的认识,它并没有深入到知识点的核心和本质。而真正的理解,则要求我们能够将新知识与已有的知识框架进行有机的整合,形成一个完整、连贯的认知结构。这就像是建造一栋房子,单纯地搬运砖块并不能解决问题,关键是要知道如何将这些砖块按照正确的顺序和方式砌建成一座稳固的房子。
验证我的观点
今天,我们就来打破这个僵局,从基础到深入,彻底揭开跨域的神秘面纱。
正文
换个思维理解什么是跨域
想象一下你住在一个小区里,你可以自由地在小区内走动,访问任何一个你想去的地方。但是,如果你想去对面小区找朋友借个充电器,通常会有小区的门禁挡住去路。这个小区的门禁就为了阻止了你直接进入另一个区域,它有效的保障了自己小区内的人员安全,为了防止小偷、抢劫犯、推销人员等等。
但是它的缺点也不言而喻,限制了你走亲访友的自由。
现在,把“小区”想象成“网站”,每个网站都有自己的规则和限制。当你试图从一个网站(我们称之为“网站A”)获取信息或与之交互时,网站A的规则不允许你这样做,除非你通过它特定的“通行证”来证明你的身份和意图。这种情况就叫做“跨域”,他的规则就是“浏览器的同源策略”。
同样,跨域也限制了Web应用的灵活性,所以我们需要解决跨域。
从“道”的层面理解跨域的解决方法
我们再来讲小区,难道区区一个门禁就能阻止外人进入了么?
当然不是,那手段太多了,这里列举几个
1 JSONP(JSON with Padding)
这是一个古老的方法。
JSONP可以理解帮忙跑腿,你小区的门是上个年代的,有门缝(<script>标签),你认识那个小区的门卫大爷,你让大爷帮你去拿充电器,然后再回来通过你小区的门缝塞给你。整个过程都在你的监督之下完成的,确保了安全性。
所以说<script>标签实际上是绕过了浏览器的同源策略限制。
理解完了看实例:
(1)前端部分
<head>
<script type="text/javascript">
// 你发现了门缝,大爷从这里给你塞充电器
function handleResponse(data) {
console.log(data.message); // 输出: 给你充电器
}
$.ajax({
url: 'https://friends.com/api/data',
type: 'GET',
dataType: 'jsonp',
jsonpCallback: 'handleResponse',
success: function(response) {
// 这里的success不会被执行,因为我们使用了JSONP
console.log(response);
}
});
</script>
</head>
(2)后端部分
// 大爷get到你的需求了,你要拿充电器“/api/data”
app.get('/api/data', (req, res) => {
const data = { message: '给你充电器' }; // 取到充电器了
res.jsonp(data); // 通过门缝塞给你
});
2. Document.domain(文档域设置)
你和你朋友决定一起干点大事,你们决定一起调整门禁系统,你们把自己小区的门禁都破解了(将双方的“document.domain”属性都设置为相同主域名""),这样你们就都能进入对方的区域,畅通无阻了。
(PS:这里只是举例做学术研究,大家不要模仿)
理解完了看实例:
(1)朋友小区html:源域名"朋友小区.房产商.com"
<head>
<script type="text/javascript">
// 当文档加载完成时执行
window.onload = function() {
// 破解门禁
document.domain = "房产商.com";
// 现在可以访问"你家.房产商.com"了
var iframe = document.createElement("iframe");
iframe.src = "http://朋友小区.房产商.com/xxx.html";
document.body.appendChild(iframe);
// 添加一个事件监听器,当iframe中的页面加载完毕时触发
iframe.onload = function() {
// 现在iframe中的页面和当前页面属于相同的主域
alert("会面成功!");
};
};
</script>
</head>
(2)你小区.html:源域名"你小区.房产商.com"
<head>
<script type="text/javascript">
// 当文档加载完成时执行
window.onload = function() {
// 破解门禁
document.domain = "房产商.com";
// 现在可以访问朋友家了
// 例如,可以在这里编写代码来与"朋友小区.房产商.com"上的脚本进行交互
};
</script>
</head>
3. window.postMessage()
你们俩在各自小区门外都做了个暗格,放了个带密码的保险柜(window.postMessage),通过它,你们可以方向的互相借东西,只有你俩才知道密码并打开。
(PS:这里只是举例做学术研究,大家不要模仿)
理解完了看实例:
(1)朋友小区.html
<head>
<script type="text/javascript">
// 朋友给你的保险柜放东西
function sendMessageToReceiver() {
// 这里假设我们已经有了目标窗口的引用,存储在变量receiverWindow中
var receiverWindow = getReceivingWindow();
// 创建要发送的消息对象
var message = {
text: '充电器给你。',
origin: 'http://朋友小区.房产商.com'
};
// 发送消息到目标窗口
receiverWindow.postMessage(message, 'http://你小区.房产商.com');
}
// 朋友查收你给朋友放的东西
function getReceivingWindow() {
// 这里返回的是目标窗口的引用,具体的获取方式取决于实际的应用场景
}
// 假设这个函数是在某个事件(如按钮点击)中被调用
function handleButtonClick() {
sendMessageToReceiver();
}
// 给按钮添加点击事件监听器
document.getElementById('sendButton').addEventListener('click', handleButtonClick);
</script>
</head>
<body>
<button id="sendButton">发送消息</button>
</body>
(2)你小区.html
<head>
<script type="text/javascript">
// 查看自己小区保险柜
function receiveMessageFromSender(event) {
// 在这里处理来自朋友.html的消息
console.log("收到物品:", event.data.text);
// 如果需要,可以向发送者发送确认消息
event.source.postMessage({ received: true }, event.origin);
}
// 添加事件监听器来接收来自其他窗口的消息
window.addEventListener("message", receiveMessageFromSender, false);
// 初始化代码,例如设置页面的标题等
window.onload = function() {
document.title = "保险柜准备好了";
};
</script>
</head>
4. CORS(跨源资源共享)
你们俩的小区是同一个开发商的,你们俩向开发商申请了一张高权限的门禁一卡通(CORS Header),里面登记了你和你朋友的信息(在请求头添加一个身份来源标识),下次再串门刷门禁,系统就会知道你们已经被授权可以自由出入两个小区了。
开发商可以理解为服务器,在服务器设置了支持访问的源,这样服务器端可以根据标识判断是否可以访问并给予响应。
理解完了看实例:
(1)服务端
@Configuration
public class CorsGlobalConfigurer implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://friends.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS");
}
}
(2)用户端
axios.get('http://friends.com/api/data')
.then(function (response) {
console.log('朋友回复:', response.data); // 充电器来了
})
.catch(function (error) {
console.log('error:', error);
});
5. Proxy Serve(代理服务器)
社会的发展衍生出了一个新职业,专业跑腿机构,例如外卖骑手,假设外卖骑手都是经过实名认证和征信备案的。他是连接你和你朋友的桥梁(代理),他不仅有能力进入两个小区,还能确保充电器不会丢失(信息的准确传递)。而你,作为雇佣跑腿的人,只需要关注充电器本身(信息本身),而无需担心如何越过那道门禁。
理解完了看实例:
webpack.config.js
module.exports = {
// ... 其他配置 ...
devServer: {
proxy: {
'/api': 'http://外卖骑手:3000' // 将去朋友家带充电器'/api'的请求让外卖骑手代理
}
}
// ... 其他配置 ...
};
总结
理解了跨域的本质,你就能明白,解决跨域并非只是背一些理论概念,学习几种技术手段,更重要的是理解背后的安全考量和设计思路。
无论是在面试中还是在实际开发中,能够深入剖析跨域的原理,展示出对Web安全和浏览器机制的深入理解,根据具体需求和浏览器的兼容性情况选择合适的跨域解决方案。
同时,为了保证网络通信的安全性,开发者在使用这些技术时也应该注意防范潜在的安全风险,如XSS攻击和CSRF攻击等。
现在,你是否对跨域有了更清晰的认识呢?不再只是机械地记忆解决方案,而是能够从更高的角度去理解和应用,这才是真正节省时间,提升技能的方式。希望这篇文章能帮你拨云见日,让跨域不再是你的困扰。
心得
初次尝试,尚显稚嫩。期待在未来的日子里,与大家共同成长,共同进步。
希望大家能喜欢我的作品,并给予我更多的支持和鼓励。谢谢大家!