知识点
- 同源策略
- 跨域
- JSONP
- CORS(服务端支持)
- document.domain
- window.name
- window.postMessage方法
- iframe加form
- 代理 Nginx配置
同源策略
- ajax 请求时,浏览器要求当前网页和server必须同源(安全)
- 同源:协议,域名,端口,三者必须一致
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
跨域
跨域,指的是从一个域名去请求另外一个域名的资源。即跨域名请求!跨域时,浏览器不能执行其他域名网站的脚本,是由浏览器的同源策略造成的,是浏览器施加的安全限制。
跨域的严格一点来说就是只要协议,域名,端口有任何一个的不同,就被当作是跨域。
加载图片 CSS JS 可无视同源策略
<img src="跨域的图片地址" />
<link href="跨域的css地址" />
<script src="跨域的js地址"></script>
提示:
我们有时候就会引入一些cdn 的js和css等,这些地址肯定不是同源的,但却可以使用。
另外,有些图片如果做了防盗链限制(服务端上的处理)的话,就不能使用。
感兴趣的请点击我的另一篇博客:图片视频等资源中转,用nodejs解决防盗链问题
Oh my god! 那为什么图片 CSS JS 可无视 同源策略???
浏览器这样做,都是有一定的考虑的,都是为了有一些功能,比如说:
<img />
可用于统计打点,可使用第三方统计服务<link />
<script>
可使用CDN,CDN一般都是外域<script>
可实现JSONP
所有的跨域,都必须经过server端允许和配合
未经server端允许就实现跨域,说明浏览器有漏洞,危险信号
那么 解决跨域问题的几个办法来了!!!
No.1 JSONP
首先,我们要先明白
访问 https://www.baidu.com/,服务端一定会返回一个html文件吗?
服务端可以任意动态拼接数据返回,只要符合html格式要求。
同理于: <script src="xxxxxxxx/getData.js">
服务端也许并不是返回一个getData.js静态文件,而是服务端通过拼接任何数据返回给你,只要拼接的数据格式不报错就行。
那么,我们已经知道了
<script>
可绕过跨域限制- 服务器可以任意动态拼接数据返回
- 所以,
<script>
就可以获得跨域的数据,只要服务端愿意返回
以上即是JSONP的实现原理,概括如下:
在HTML
标签里,一些标签比如script
,img
这样的获取资源的标签是没有跨域限制的。
动态插入script
标签,通过script
标签引入一个js
文件,这个js
文件载入成功后会执行我们在url
参数中指定的函数,并且会把我们需要的json
数据作为参数传入
优点: 兼容性好,简单易用,支持浏览器与服务器双向通信。
缺点: 只支持GET请求。
简易代码示例:
后端被请求文件:jsonp.js (由后端处理拼接生成)
// jsonp.js 文件内容
abc(
{ name: 'xxx' }
)
前端请求代码:
<script>
window.abc = function (data) {
console.log(data)
}
</script>
<script src="http://localhost:8002/jsonp.js?username=xxx&callback=abc"></script>
代码讲解:
前端通过<script>
请求http://localhost:8002/jsonp.js
并传入相应参数?username=xxx&callback=abc
后端收到请求,根据路径和参数等信息,动态处理拼接出jsonp.js
文件返回给前端。
前端根据jsonp.js
文件内容,执行window.abc
函数,输出内部数据。
No.2 CORS
CORS是一个w3c标准,全称是"跨域资源共享"(Cross-origin resource sharing),当一个请求url的协议,域名,端口三者之间任意与当前页面地址不同即为跨域.它允许浏览器向跨源服务器发送XMLHttpRequest请求,从而克服AJAX只能同源使用的限制.
原理: 服务器对于CORS
的支持,主要就是通过设置Access-Control-Allow-Origin
来进行的。如果浏览器检测到相应的设置,就可以允许Ajax
进行跨域的访问。
浏览器将CORS请求分为两类:简单请求和非简单请求
只要满足以下两大条件,就属于简单请求:
- 请求方法是以下三种方法之一:
HEAD
GET
POST
- HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
:只限于三个值application/x-www-form-urlencoded
、multipart/form-data
、text/plain
不同时满足上面两个条件,就是非简单请求
代码简易示例: CORS - 服务器设置 http header
// 第二个参数填写允许跨域的域名称,不建议直接写"*"
response.setHeader("Access-Control-Allow-Origin","http://localhost:8080");
response.setHeader("Access-Control-Allow-Headers","X-Requested-With");
response.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
// 接收跨域的cookie
response.setHeader("Access-Control-Allow-Credentials","true");
No.3 通过修改document.domain
来跨域
将子域和主域的document.domain
设为同一个主域。
前提条件:这两个域名必须属于同一个基础域名,而且所用的协议,端口都要一致,否则无法使用document.domain
来进行跨域。
详细信息请看
No.4 使用window.name来进行跨域
window
对象有个name属性,该属性有个特征:即在一个窗口(window
)的生命周期内,窗口载入的所有页面都是共享一个window.name
的,每个页面对window.name都有读写的权限,window.name
是持久存在一个窗口载入过的所有页面中的。
No.5 使用HTML5
中新引进的window.postMessage方法来跨域传送数据
window.postMessage()是HTML5的一个接口,专注实现不同窗口不同页面的跨域通讯。
代码示例发送方:
<template>
<div>
<button @click="postMessage">给http://crossDomain.com:9099发消息</button>
<iframe name="crossDomainIframe" src="http://crossdomain.com:9099"></iframe>
</div>
</template>
<script>
export default {
mounted () {
window.addEventListener('message', (e) => {
// 这里一定要对来源做校验
if (e.origin === 'http://crossdomain.com:9099') {
// 来自http://crossdomain.com:9099的结果回复
console.log(e.data)
}
})
},
methods: {
// 向http://crossdomain.com:9099发消息
postMessage () {
const iframe = window.frames['crossDomainIframe']
iframe.postMessage('我是[http://localhost:9099], 麻烦你查一下你那边有没有id为app的Dom', 'http://crossdomain.com:9099')
}
}
}
</script>
代码示例接收方:
<template>
<div>
我是http://crossdomain.com:9099
</div>
</template>
<script>
export default {
mounted () {
window.addEventListener('message', (e) => {
// 这里一定要对来源做校验
if (e.origin === 'http://localhost:9099') {
// http://localhost:9099发来的信息
console.log(e.data)
// e.source可以是回信的对象,其实就是http://localhost:9099窗口对象(window)的引用
// e.origin可以作为targetOrigin
e.source.postMessage(`我是[http://crossdomain.com:9099],我知道了兄弟,这就是你想知道的结果:${document.getElementById('app') ? '有id为app的Dom' : '没有id为app的Dom'}`, e.origin);
}
})
}
}
</script>
No.6 iframe加form
JSONP只能发送GET请求,因为本质上script加载资源就是GET。如果要发送POST请求可以如下
后端代码:
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
static async iframePost (ctx) {
let postData = ctx.request.body
console.log(postData)
ctx.body = successBody({postData: postData}, 'success')
}
}
module.exports = CrossDomain
前端代码:
const requestPost = ({url, data}) => {
// 首先创建一个用来发送数据的iframe.
const iframe = document.createElement('iframe')
iframe.name = 'iframePost'
iframe.style.display = 'none'
document.body.appendChild(iframe)
const form = document.createElement('form')
const node = document.createElement('input')
// 注册iframe的load事件处理程序,如果你需要在响应返回时执行一些操作的话.
iframe.addEventListener('load', function () {
console.log('post success')
})
form.action = url
// 在指定的iframe中执行form
form.target = iframe.name
form.method = 'post'
for (let name in data) {
node.name = name
node.value = data[name].toString()
form.appendChild(node.cloneNode())
}
// 表单元素需要添加到主文档中.
form.style.display = 'none'
document.body.appendChild(form)
form.submit()
// 表单提交后,就可以删除这个表单,不影响下次的数据发送.
document.body.removeChild(form)
}
// 使用方式
requestPost({
url: 'http://localhost:9871/api/iframePost',
data: {
msg: 'helloIframePost'
}
})
No.7 代理 Nginx配置
server{
# 监听9099端口
listen 9099;
# 域名是localhost
server_name localhost;
#凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
location ^~ /api {
proxy_pass http://localhost:9871;
}
}
// 请求的时候直接用回前端这边的域名http://localhost:9099,这就不会跨域,然后Nginx监听到凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
fetch('http://localhost:9099/api/iframePost', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
msg: 'helloIframePost'
})
})
其他有趣的跨域问题
- canvas操作图片的跨域问题
参考地址
帅气逼人的家伙们,赏个赞呗~ _(¦3」∠)_
前端知识体系 目录结构