什么是跨域
当a.knoci.cn名下的页面或脚本试图去请求b.knoci.cn域名下的资源时,就是典型的跨域行为。跨域的定义从受限范围可以分为两种,广义跨域和狭义跨域。
广义跨域
广义跨域通常包含以下三种行为:
资源跳转:a链接、重定向。
资源嵌入:
<link>、<script>、<img>、<frame>
等dom
标签,还有样式中background:url()、@font-face()等文件外链。脚本请求:浏览器存储数据读取、
dom
和js
对象的跨域操作、js
发起的ajax
请求等。
其中,资源跳转和资源嵌入行为可以正常请求到跨域资源,脚本请求在未经任何处理的情况下,通常会有跨域问题。
狭义跨域
狭义跨域正是浏览器同源策略限制的一类请求场景,即我们通常说的跨域行为,通常包含以下三种行为:
cookie、localStorage和indexDB无法读取。
dom和js对象无法获取和操作。
ajax请求无法发送。
为什么会跨域
说到跨域不得不谈的就是浏览器的同源策略,跨域也是因为浏览器这个机制引起的,这个机制的存在还是在于安全。
什么是源
Web内容的源由用于访问它的URL 的方案(协议),主机(域名)和端口定义。只有当方案,主机和端口都匹配时,两个对象具有相同的起源。
同源不同源一句话就可以判断:就是url中 scheme
host
port
都相同即为同源。
下面认识下url 结构中的这三个部分。
URL结构
URL 代表着是统一资源定位符(Uniform Resource Locator)。URL 无非就是一个给定的独特资源在 Web 上的地址。
URL有如下结构组成:
- Schme 或者 Protocol

- Domain Name 也叫做host域名

- port 端口号

- Parameters参数

- Anchor 锚点,一般用于定位位置

同源不同源举例
举一下同源不同源的例子,便于理解:
同源例子
例子 | 原因 |
---|---|
http://example.com/app1/index.html http://example.com/app2/index.html | 相同的 scheme http 和host |
http://Example.com:80 http://example.com | http 默认80端口所以同源 |
不同源例子
例子 | 原因 |
---|---|
http://example.com/app1 https://example.com/app2 | 不同的协议 |
http://example.com http://myapp.example.com | 不同的host |
http://example.com http://example.com:8080 | 不同的端口 |
浏览器为什么需要同源策略
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。一个页面:
- 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
- 无法接触非同源网页的 DOM
- 无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)
常规前端请求跨域
在没有前后端分离的时候,跨域问题往往是很少的。因为前后端都部署到一起。现在前后端分离不管vue /react 面临跨域请求的问题。
下面是引用官网描述的一张图来解释跨域:

跨源域资源共享(CORS)机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch)使用 CORS,以降低跨源 HTTP 请求所带来的风险。
前端处理跨域
jsonp跨域
jsonp (JSON with Padding)的原理非常简单,就是HTML标签中,很多带src属性的标签都可以跨域请求内容,比如我们熟悉的img图片标签。同理,script标签也可以,可以利用script标签来执行跨域的javascript代码。通过这些代码,我们就能实现前端跨域请求数据。
jsonp 可以在前端解决跨域问题,但是只是针对于get请求。实现方式可以引用一些npm 第三方库实现,jquery 也是带的。
jsonp跨解实现流程:

jsonp跨域代码示例:
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参callback给后端,后端返回时执行这个在前端定义的回调函数
script.src = 'http://a.qq.com/index.php?callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res));
}
jsonp跨域优点:
- jsonp兼容性强,适用于所有浏览器,尤其是IE10及以下浏览器。
jsonp跨域缺点:
没有关于调用错误的处理。
只支持GET请求,不支持POST请求以及大数据量的请求,而且也无法拿到相关的返回头,状态码等数据。
callback参数恶意注入,可能会造成xss漏洞。
无法设置资源访问授权。
webpack-dev-server
前端无论是vue项目还是react 项目大多数都会以webpack-dev-server 来运行,webpack-dev-server 可以设置代理,前端可以在开发环境设置代理解决跨域问题。
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: { '^/api': '' },
changeOrigin: true,
},
}
vue-cli、create-react-app、umi 等脚手架找到webpack devserver配置位置配上即可。
注意: 只限在开发环境,生产环境需要web 服务器同样原理代即可。下面会说明怎么用。
document.domain
利用document.domain
可以修改 访问页面的域,多用在父子关系的域,子域可以设置document.domain等于父域,从而解决同父域跨域问题。

举例子:
www.a.knoci.cn访问www.b.knoci.cn跨域,可以利用 document.domain
设置成 www.knoci.cn
代码示例:
<!-- A页面 http://a.qq.com/a.html -->
<iframe id="iframe" src="http://b.qq.com/b.html"></iframe>
<script>
document.domain = "qq.com";
var windowB = document.getElementById("iframe").contentWindow;
alert("B页面的user变量:" + windowB.user);
</script>
<!-- B页面 http://b.qq.com/b.html -->
<script>
document.domain = "qq.com";
var user = "saramliu";
</script>
document.domain+iframe方案优点:
- 实现逻辑简单,无需额外中转页面
document.domain+iframe方案缺点:
- 仅适用于主域相同,子域不同的前端通信跨域场景
后端处理跨域
Http配置CORS
跨域其实也是http层面上可以解决的问题,后端解决也是比较简单的,也是项目常见的解决手法。
CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应。
同源安全策略 默认阻止“跨域”获取资源。但是 CORS 给了web服务器这样的权限,即服务器可以选择,允许跨域请求访问到它们的资源。
- Access-Control-Allow-Origin 指示请求的资源能共享给哪些域。
- Access-Control-Allow-Credentials 指示当请求的凭证标记为 true 时,是否响应该请求。
- Access-Control-Allow-Headers 用在对预请求的响应中,指示实际的请求中可以使用哪些 HTTP 头。
- Access-Control-Allow-Methods 指定对预请求的响应中,哪些 HTTP 方法允许访问请求的资源。
- Access-Control-Expose-Headers 指示哪些 HTTP 头的名称能在响应中列出。
- Access-Control-Max-Age 指示预请求的结果能被缓存多久。
- Access-Control-Request-Headers 用于发起一个预请求,告知服务器正式请求会使用那些 HTTP 头。
- Access-Control-Request-Method 用于发起一个预请求,告知服务器正式请求会使用哪一种 HTTP 请求方法。
- Origin 指示获取资源的请求是从什么域发起的。
gin框架设置跨域:
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, x-token")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH, PUT")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
}
}
Nginx
添加响应头(CORS)
最常见的方式是在 Nginx 配置中添加Access - Control - Allow - Origin
等相关响应头来允许跨域访问。
server {
listen 80;
server_name your_domain.com;
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User - Agent,X - Requested - With,If - Modified - Since,Cache - Control,Content - Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content - Length,Content - Range';
# 以下是正常的代理转发等配置,根据实际情况而定
proxy_pass http://backend_server;
}
}
add_header 'Access - Control - Allow - Origin' '*';
:这一行允许来自任何域的跨域请求。如果只想允许特定的域,可以将*
替换为具体的域名,如http://example.com
。add_header 'Access - Control -Allow - Methods' 'GET, POST, OPTIONS';
:指定允许的 HTTP 请求方法。除了常见的GET
和POST
,还添加了OPTIONS
方法。OPTIONS
方法通常用于浏览器在实际请求之前发送的预检请求,用于检查服务器是否允许跨域请求。add_header 'Access - Control -Allow - Headers' 'DNT,User - Agent,X - Requested - With,If - Modified - Since,Cache - Control,Content - Type,Range';
:定义允许的请求头。当客户端发送带有自定义请求头的跨域请求时,需要在这里明确允许这些请求头,否则请求可能会被拒绝。add_header 'Access - Control -Expose - Headers' 'Content - Length,Content - Range';
:指定哪些响应头可以暴露给客户端。有些响应头默认是不暴露的,通过这个配置可以让客户端访问这些响应头。
处理预检请求(OPTIONS)
当浏览器发送跨域请求时,对于非简单请求(例如包含自定义请求头、非GET
/POST
/HEAD
方法等),会先发送一个OPTIONS
预检请求。Nginx 需要正确处理这个预检请求。
server {
listen 80;
server_name your_domain.com;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User - Agent,X - Requested - With,If - Modified - Since,Cache - Control,Content - Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content - Length,Content - Range';
return 204;
}
# 正常的代理转发等配置
proxy_pass http://backend_server;
}
}
if ($request_method = 'OPTIONS')
:这个条件判断请求方法是否为OPTIONS
。如果是,就执行下面的配置。- 内部的
add_header
指令和前面类似,用于添加允许跨域的响应头。return 204;
表示返回一个空的响应体,状态码为204
(无内容)。这是处理OPTIONS
预检请求的常见方式,告诉浏览器服务器允许跨域请求,并且可以继续发送实际的请求。
总结
跨域限制像道墙,同源策略紧守望。
前端后端不同网,请求数据遇阻挡。
允许源址要得当,方法头域细思量。
预检 OPTIONS 别忘,CORS 头是好良方。
安全合规不能放,合理配置跨域爽。
Comments NOTHING