整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

前端福利】详解 JavaScript 的跨域基础知识

前端福利】详解 JavaScript 的跨域基础知识及示例


域(Cross-origin)是 Web 开发中经常会遇到的一种问题,本文将彻底剖析前端开发中跨域问题的各种表现和原因,深入探讨几种跨域解决方案,并提供对应的示例代码。

一、什么是跨域

同源策略(Same-origin policy)是 Web 安全的重要策略之一,它规定了不同源之间的明确分界线,源指的是协议、主机和端口号的组合。

同源策略的实质是一个域下的文档或脚本不能获取另一个域下的内容。但是网络上存在着很多需要进行跨域操作才能正常访问的网页,比如 CDN、第三方登录、跨域 AJAX、IFrame 和跨域资源嵌入等等。


二、跨域表现形式

1. AJAX 跨域:

XMLHttpRequest 和 Fetch 在发起跨域请求时会出现如下异常:


XMLHttpRequest cannot load http://example.com/path. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.

2. 动态添加 script 标签跨域:

动态添加的 script 标签可以跨域访问资源,但能获取到的数据受到服务器数据格式及隐私策略的限制,此方法一般用于实现 JSONP 解决跨域问题。


3. 资源跨域嵌入:

在 HTML 代码中,使用跨域资源嵌入(Cross-origin Resource Embedding, CORS)实现跨域,浏览器会发起预检请求(Options),询问服务器是否支持特定的跨域请求,随后发起真正的请求,如果发现请求的资源没有跨域限制,则会正常访问,否则会出现跨域异常。


三、跨域解决方案

1. JSONP

JSONP(JSON with Padding),是通过把 JSON 数据作为参数传递到一个函数中,该函数在回调中处理 JSON 数据。一般来说,JSONP 数据的传输方式是通过 script 标签的 src 属性进行跨域请求,因为 script 标签的跨域限制很少。

服务器返回的数据格式应该是以下的格式:

callbackName({"name": "Lucy", "age": 18})


实现一个简单的 JSONP:

function createJsonp(url, callback) {

const script=document.createElement('script');

script.setAttribute('type', 'text/javascript');

script.setAttribute('src', url);

document.body.appendChild(script);

window[callback]=function(data) {

callback(data);

document.body.removeChild(script);

}

}


调用方式:

createJsonp('http://example.com/api?callback=showData', 'showData');


2. CORS

CORS 是解决跨域最常用的解决方案之一,其原理是服务器在响应中添加如下 Header 信息:


Access-Control-Allow-Origin: http://localhost:8000

Access-Control-Allow-Methods: GET, POST, PUT

Access-Control-Allow-Headers: X-Custom-Header

Access-Control-Allow-Credentials: true


Access-Control-Allow-Origin 表示允许访问的来源地址,其取值可以是 *,表示允许来自任何地址的请求。

Access-Control-Allow-Methods 表示允许的 HTTP 请求方法。

Access-Control-Allow-Headers 表示允许的 HTTP 请求头。

Access-Control-Allow-Credentials 表示是否允许携带凭证信息,如 cookie。

客户端的请求代码:

const xhr=new XMLHttpRequest();

xhr.open('GET', 'http://example.com/api', true);

xhr.withCredentials=true; // 携带 cookie

xhr.onload=function() {

console.log(xhr.responseText);

}

xhr.send();

3. postMessage

由于同源策略的限制,在一个页面中嵌入另一个页面无法直接访问并操作另一个页面的 DOM 元素,为了解决这一问题,可以使用 postMessage 来实现跨域通信。

代码示例:

// 主页面:http://localhost:8000/main

window.onload=function() {

const iframe=document.createElement('iframe');

iframe.setAttribute('src', 'http://example.com/iframe.html');

iframe.style.display='none';

document.body.appendChild(iframe);

window.addEventListener('message', function(event) {

console.log(event.origin);

console.log(event.data);

});

}

//iframe页面:http://example.com/iframe.html

const targetOrigin='http://localhost:8000';

window.parent.postMessage('message from iframe', targetOrigin);


四、总结

跨域问题不是前端开发人员可以绕过的,因为不同的场合会有不同的解决方案,常见的解决方案有 JSONP、CORS 和 postMessage。所以熟悉 Web 安全,了解跨域问题的知识显得至关重要,如果你能够深入理解和掌握跨域问题,相信你在 Web 开发领域的技术影响力也会更大。

本期文章的视频内容可以去 B 站搜索看,视频内容有完整的分析和演示

在 BS 架构的项目中以及前端开发编码过程中有可能会遇到跨域的问题,而跨域问题随着现代 web 开发很多项目采用前后端分离的方式进行分工合作再一次成为前端开发过程中都会遇到问题。曾经有一段时间 JSONP 的一度成为很多跨域问题的解决方案。在个人印象中曾经有一段时期各个公司关注于建站建官网进行 SEO,那个时候很多开发人员都会在前端粘贴一段脚本用来监控对网站的请求,那个时候跨域问题开始进入我个人的视野。后来随着 Vue、Angular、React 的兴起刮起了前端开发变革的风,前后分离的开发方式让跨域问题再一次成为需要面对的问题。

所谓跨域问题实际上就是浏览器的一个非常核心的安全策略,有很多方法可以来解决这个问题,各个方法使用场景各不相同。在项目中有有使用过几个解决方案,在这里做归纳总结

什么是跨域请求

在前端开发编码过程中,常见的 html 标签例如:aformimgscriptlinkiframe以及 Ajax 操作都可以指向一个资源地址或者说可以发起对一个资源的请求,那么这里所说的请求就存在同域请求还是跨域请求。

所谓跨域请求就是指:当前发起请求的域与该请求指向的资源所在的域不一致(这里所有的域是协议、域名和端口号的合集,同域就是所协议、域名和端口号均相同,任何一个不同都是跨域)。

常见的跨域场景有:

常见的跨域场景

同源策略(same-origin pllicy)

同源策略是由 Netscape 提出的一个著名的安全策略,它是浏览器最基本最核心的安全功能。如果缺少了同源策略,则浏览器的正常功能都会受到影响,可以说 web 是构建在同源策略基础上的,浏览器是针对同源策略的一种实现。

同源策略是浏览器的行为,是为了保护本地数据不被 Javascript 代码获取回来的数据污染,因此拦截的是客户端发出请求后回来的数据接收,即请求发送了,服务器响应了,但无法被浏览器接收执行,在现代浏览器中违反同源策略的跨域请求会在控制台直接报错。

chrome控制台报错信息

同源策略的具体表现:

  • DOM 层面的同源策略,限制了来自不同源的Document对象或者 JS 脚本对当前Document对象的读取或设置某些属性
  • Cookie 和 XMLHttpRequest 层面的同源策略,默认情况下禁止 Ajax 直接进行跨域请求(请求可以发起,但接收结果等操作被浏览器拦截并在控制台报错),cookie 层面数据默认不能跨域访问
  • 特定 HTML 标签的的策略,aformimgscriptlinkiframe等这些有src属性的标签可以对资源跨域访问和执行

为什么会有这些限制

同源策略是浏览器最核心基础的一个安全策略,是浏览器基于安全的需要来进行的限制。常见的CSRF、XSS攻击都与之有关联。在这里就不做介绍,有兴趣的读者可以自行去搜索了解下这些攻击的原理

跨域请求为什么会出现在前端开发中?

既然同源策略是浏览器的核心基础安全策略,那为什么我们在进行前端开发特别是 Ajax 调用时还要进行跨域请求呢?同源策略是用来防御来自非法的攻击,但我们不能因为防御非法的攻击就将所有的跨域都拦截掉。

在现代前端开发中,我们经常需要调用第三方的服务接口(例如 mock server、fake api),随着专业化分工的出现有很多专业的信息服务提供商为前端开发者提供各类接口,这种情况下就需要进行跨域请求(这类前端接口服务很多是采用的 cors 方式来解决跨域问题的,下文会详细介绍)。

还有一类情况是在前后端分离的项目中,前端后端分属于不同的服务跨域问题在采用这种架构的时候就存在。而且现在很多项目都采用这种前后分离的方式,这类项目很多是会采用反向代理的方式来解决跨域问题。

Ajax 跨域请求如何实现

这里所说的跨域请求解决方案主要是针对 Ajax 请求

修改浏览器的安全设置(不推荐)

既然是浏览器的安全策略,那么最简单粗暴的方法就是禁用这个策略。这种操作很危险,不推荐使用。在此也不做具体的介绍,在跨域问题刚出来的时候有人采用这种方法,目前已很少有人采用。

JSONP

JSONP(JSON with Padding)是 JSON 的一种使用模式,可用于解决主流浏览器的跨域数据访问问题,在早两三年前端解决跨域问题中经常出现这类解决方案。

JSONP 的原理就是,Ajax 存在跨域安全问题但是 script 标签是不存在这类问题的,于是乎就有人根据这个特性做文章找解决方案。

remote.com/remote.js

remoteFunction('remote)

index.html

<script src="http://remote.com/remote.js" type="text/javascript"></script>
<script>
remoteFunction()
</script>

我们都知道这种方式是可以成功的,因此进一步改造:

remote.com/remote.js

let data={
 code: 200,
 msg: "data from remote"
};
localFunction(data);

index.html

<script>
localFunction(data) {
 console.log(data)
}
</script>
<script src="http://remote.com/remote.js" type="text/javascript"></script>

通过这一步的改造就可以发现我们的本地函数的参数是来自于远程服务,在远程 remote.js 根据业务逻辑传递参数到本地函数进行的调用。

于是更进一步,将代码写死的 remote.js 进行动态的生成,我们以后台 PHP 语言为例:

remote.php

<?php
//通过设置content-type能够指明返回的内容类型
header('Contetn-type:application/json');
$callbackFunction=htmlspecialchars($_GET['callback']);
$data='data from remote';
echo $callbackFunction.'('.$data.')';

index.html

<script type="text/javascript">
 localFunction=function(data) {
 console.log(data);
 };
</script>
<script
 src="http://localhost:3000/remote.php?callback=localFunction"
 type="text/javascript"
></script>

这种实现方式就是 JSONP 的简单实现,JSONP 的核心理念就是利用 script 可以进行跨域请求,通过跨域请求将业务处理逻辑的回调函数通过 url 参数的形式发给远程请求,远程请求通过数据库调用获取到前端需要的数据后,将发送过来的回掉函数以及数据参数进行拼装,生成一段可执行的 JavaScript(json)代码,这样当这次请求完成后,对应的业务处理函数也就对应的执行了。

JSONP 有其天然的缺陷,因为是通过 script 的 src 进行的资源请求,所以都是 GET 方式,其他的 POST、PUT、DELETE 并不支持。这种采用这种解决方式越来越少。

跨域资源共享 CORS(Cross-Origin Resource Sharing)

CORS 是一个新的 W3C 标准,它新增的一组 HTTP 首部字段允许服务器其声明哪些来源请求有权限访问哪些资源,换言之它允许浏览器向其声明了 CORS 的站进行跨域请求。

这种方式最主要的特点就是会在响应的 HTTP 首部增加 Access-Control-Allow-Origin 等信息,从而判定哪些资源站可以进行跨域请求,还有几个其他相关的 HTTP 首部进行更加精细化的控制,最主要的还是 Access-Control-Allow-Origin。具体每个首部信息的含义可以去搜索详细了解下。

我们以 Express 搭建的远程服务为例来说明:

var express=require("express");
var cors=require("cors");
var app=express();
//使用express的cors中间件使其支持跨域请求
app.use(cors());
app.get("/", function(req, res, next) {
 res.json({ msg: "This is CORS-enabled for all origins!" });
});
app.listen(3000, function() {
 console.log("CORS-enabled web server listening on port 80");
});

针对支持 CORS 的服务发起 Ajax 请求最大的特定,客户端即浏览器首先会发送一次请求到服务端判断服务端是否支持跨域请求及是否合法,如果判断通过会回复信息给客户端浏览器,浏览器通过收到的回复信息判断服务端对这次跨域请求是否支持,如果支持就再发送实际的业务请求。所以在这里会有两次请求。

CORS 与 JSONP 对比来说优势比较明显,JSONP 只支持 GET 方式局限性很多,而且 JSONP 并不符合处理业务的正常流程。采用 CORS 的方式,前端编码与正常非跨域请求没有什么不同。在目前很多的 Fake API (模拟接口服务)、Mock Server(数据模拟服务)以及其他公共服务上都很多采用 CORS 的方式来解决跨域问题,例如 json-server 等。

iframe

iframe 与 JSONP 都是使用 src 属性没有跨域限制的特性,iframe 这种方式也不推荐使用,不做详细介绍。

反向代理

既然不能跨域请求,那么我们不跨域就可以了。通过在请求到达服务前部署一个服务,将接口请求进行转发,这就是反向代理。通过一定的转发规则可以将前端的请求转发到其他的服务。以 Nginx 为例:

server {
 listen 9999
 server_name localhost
 #将所有localhost:9099/api为开头的请求进行转发
 location ^~ /api {
 proxy_pass http://localhost:3000;
 }
}

通过反向代理我们将前端后端项目统一通过反向代理来提供对外的服务,这样在前端看上去就跟不存在跨域一样。

反向代理麻烦之处就在原对 Nginx 等反向代理服务的配置,在目前前后端分离的项目中很多都是采用这种方式。

总结

综上所述,CORS 和反向代理是目前使用最多的解决方案,这两个解决方案使用的场景并不相同,我们要根据自身的需求进行选择。公共服务、Fake API 、Mock Server 一般采用 CORS 的方案;而公司前后端分离的项目中更多是采用反向代理的方案。

前端编程中,跨域问题应该是很常见的,处理方式有很多,下边来说一说用到过的处理方式。

什么是跨域:

只要协议、域名、端口有任何一个不同,都被当做不同的域,js不能在不同的域之间进行通信和传输数据。

跨域的情况:

1、用ajax向不同的域请求数据

2、通过js获取页面中不同域的框架中的数据(常见iframe)

浏览器都有一个同源策略,其限制之一就是不能通过ajax的方法去请求不同源中的文档,限制之二是浏览器中不同域的框架之间是不能进行js的交互操作的。

跨域的方法:1、 jsonp跨域原理:创建<script>标签,利用src属性跨域(src属性可以跨域),同样<img>也可以处理跨域例子:test.html -----> http://a.haha.com/test.htmlajaxData -----> http://b.haha.com/listtest.html访问ajaxData需要跨域

通过一个script标签引入一个js文件,当js文件载入成功后会把需要的json数据作为参数传入URL中指定的函数并执行此函数,因为ajaxData被当作一个js文件来引入,所以其返回的数据必须是一个能执行的js文件,所以需要服务端的配合才行。

局限性: 需要服务端配合做处理 jsonp只支持“get”请求,不支持“post”请求

2、 document.domain来跨越子域

原理:设置相同的主域例子:一个页面,它的地址是 http://a.haha.com/test.html , 在这个页面里面有一个iframe,它的src是 http://b.haha.com/test.html , 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西的

document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。

修改document.domain的方法只适用于不同子域的框架间的交互,对ajax访问的不适用。

3、隐藏iframe做代理跨域

如果你想通过ajax的方法去与不同子域的页面交互,除了使用jsonp的方法外,还可以用一个隐藏的iframe来做一个代理。

原理:让这个隐藏的iframe载入一个与你想要通过ajax获取数据的目标页面处在相同的域的页面,所以这个iframe中的页面是可以正常使用ajax去获取你要的数据的,然后就是通过修改document.domain的方法,让我们能通过js完全控制这个iframe,这样我们就可以让iframe去发送ajax请求,然后收到的数据我们也可以获得了。

以上第3种方式是比较常见的,也是用的比较多的,当然跨域方式有好多种,欢迎有兴趣的小伙伴一起讨论。