概念
域(Domain)是Windows网络中独立运行的单位,域之间相互访问则需要建立信任关系(即Trust Relation)。信任关系是连接在域与域之间的桥梁。当一个域与其他域建立了信任关系后,2个域之间不但可以按需要相互进行管理,还可以跨网分配文件和打印机等设备资源,使不同的域之间实现网络资源的共享与管理。
广义的跨域
- 资源跳转:A链接、重定向、表单提交
- 资源嵌入:link、script、img、frame 等dom标签,还有样式中background:url()、@font-face()等文件外链
- 脚本请求:js发起的ajax请求、dom和js对象的跨域操作等
同源策略
同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略阻止从一个域上加载的脚本获取或操作另一个域上的文档属性。也就是说,受到请求的 URL 的域必须与当前 Web 页面的域相同。这意味着浏览器隔离来自不同源的内容,以防止它们之间的操作。
同源策略不阻止将动态脚本元素插入文档中。
同源策略限制以下几种行为:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 和 Js对象无法获得
- AJAX 请求不能发送
有三个标签允许跨域加载资源:
1. [外链图片转存失败(img-0pLj5Hja-1562060414880)(https://mp.csdn.net/mdeditor/XXX)]
2. <link href="XXX">
3. <script src="XXX">
常见的跨域
URL | 说明 | 是否允许通信 |
---|---|---|
http://www.small.com/a.js http://www.small.com/b.js | 同一域名 | 允许 |
http://www.small.com/a.js http://www.small.com/js/b.js | 同一域名,不同文件夹 | 允许 |
http://www.small.com:3000/a.js http://www.small.com:8000/b.js | 同一域名,不同端口 | 不允许 |
http://www.small.com/a.js https://www.small.com/b.js | 同一域名,不同协议 | 不允许 |
http://www.small.com/a.js http://192.168.25.37/b.js | 域名和域名对应IP | 不允许 |
http://www.small.com/a.js http://abc.small.com/b.js | 主域相同,子域不同 | 不允许 |
http://www.small.com/a.js http://small.com/b.js | 同一域名,不同二级域名 | 不允许 |
http://www.small.com/a.js http://www.big.com/b.js | 不同域名 | 不允许 |
解决跨域
1. JSONP
原理:
- 借助了script标签 src 请求资源时, 不受同源策略的限制
- 在服务端返回一个函数的调用,将数据当成调用函数的实参
- 在浏览器端,需要程序要声明一个全局函数,通过形参就可以获取到服务端返回的对应的值
优点: jsonp没有兼容性问题
缺点: jsonp只能用于get请求
原生JS实现跨域
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参并指定回调执行函数为onBack
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';
document.head.appendChild(script);
// 回调执行函数
function onBack(res) {
alert(JSON.stringify(res));
}
</script>
jQuery实现跨域
// 跟普通的 get请求没有任何的区别,只需要把dataType固定成jsonp即可
$.ajax({
type:"get",
url:"http://www.Jepson.com/testjs.php",
dataType:"jsonp",
data:{
uname:"zs",
upass:"123456"
},
success:function (info) {
console.log(info);
}
})
2. nginx 反向代理
原理:
- 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题
// Nginx配置
server{
# 监听9099端口
listen 9099;
# 域名是localhost
server_name localhost;
#凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
location ^~ /api {
proxy_pass http://localhost:9871;
}
}
3.CORS(跨域资源共享)
- 浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)
- 简单请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置。若要带cookie请求:前后端都需要设置。
- 非简单请求:非简单请求会发出一次预检测请求,返回码是204,预检测通过才会真正发出请求,这才返回200。这里通过前端发请求的时候增加一个额外的headers来触发非简单请求
优点: 功能强大支持各种 HTTP Method
缺点: 兼容性不如 JSONP
目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。
简单的来说,就是跨域的目标服务器要返回一系列的Headers,通过这些Headers来控制是否同意跨域。这些Headers有:
1. Access-Control-Allow-Origin HTTP Response Header
2. Access-Control-Max-Age HTTP Response Header
3. Access-Control-Allow-Credentials HTTP Response Header
4. Access-Control-Allow-Methods HTTP Response Header
5. Access-Control-Allow-Headers HTTP Response Header
6. Origin HTTP Request Header
7. Access-Control-Request-Method HTTP Request Header
8. Access-Control-Request-Headers HTTP Request Header
如果请求的url是aspx页面,则需要在aspx页面中添加代码Response.AddHeader(“Access-Control-Allow-Origin”, “*”);
如果请求的url是PHP页面,则需要在PHP页面中添加代码:header(“Access-Control-Allow-Origin: *”);
如果请求的url是静态的html页面,则需要在页面中添加meta标签代码:
<meta http-equiv="Access-Control-Allow-Origin" content="*" />
如果服务器端可以确定是要被哪些域名访问,最好是能把以上代码中的“*”代替为具体的域名,这样做可以相应的增强安全性。
4. vue 项目中实现跨域
在 config 文件夹下的 index.js 里配置(vue-cli 2.0)
// 代理到本地,模拟接口
'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.
const path = require('path')
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api': {
target: 'http://localhost:8080',
pathRewrite: {
'^/api': '/static/mock'
}
}
},
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
/**
* Source Maps
*/
// https://webpack.js.org/configuration/devtool/#development
devtool: 'cheap-module-eval-source-map',
// If you have problems debugging vue-files in devtools,
// set this to false - it *may* help
// https://vue-loader.vuejs.org/en/options.html#cachebusting
cacheBusting: true,
cssSourceMap: true
}
}
// 稍微复杂的代理
'use strict'
const path = require('path')
// 此处写上地址之后本地就可以代理上了 就可以直接本地调用接口 !!! 写上后台的域名就可以了
let proxyUrl = 'http://192.168.25.34:8080'
const devProxy = {
target: proxyUrl,
changeOrigin: true,
onProxyReq(proxyReq, req, res) {
proxyReq.setHeader('host', proxyUrl.replace('http://', ''))
proxyReq.setHeader('referer', proxyUrl)
proxyReq.setHeader('origin', proxyUrl)
},
onProxyRes(proxyRes, req, res) {
if (!res.headers) {
res.headers = {}
}
res.headers['Etag'] = proxyRes.headers['etag']
},
}
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/**/*': devProxy
},
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
/**
* Source Maps
*/
// https://webpack.js.org/configuration/devtool/#development
devtool: 'cheap-module-eval-source-map',
// If you have problems debugging vue-files in devtools,
// set this to false - it *may* help
// https://vue-loader.vuejs.org/en/options.html#cachebusting
cacheBusting: true,
cssSourceMap: true
}
}
在项目文件的根目录下创建 vue.config.js(vue2.0 / vue3.0)
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? '/' : '/',
// 输出文件目录
outputDir: 'dist',
// 静态资源目录 (js, css, img, fonts)
assetsDir: 'assets',
// 指定生成的 index.html 的输出路径
indexPath: 'index.html',
// lintOnSave:{ type:Boolean default:true } 是否使用eslint
lintOnSave: true,
// productionSourceMap:{ type:Bollean,default:true } 生产源映射
// 如果不需要生产时的源映射,那么将此设置为 false 可以加速生产构建
productionSourceMap: true,
// 默认情况下 babel-loader 会忽略所有 node_modules 中的文件。如果你想要通过 Babel 显式转译一个依赖,可以在这个选项中列出来
transpileDependencies: [],
// devServer: { type: Object } 3个属性 host, port, https, 它支持 webpack-dev-server 的所有选项
devServer: {
port: 8080,
host: 'localhost',
https: false,
open: false, // 配置自动启动浏览器
overlay: { // 浏览器 overlay 同时显示警告和错误
warnings: true,
errors: true
},
// 配置跨域处理
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: {
'^/api': '/mock'
}
}
}
}
}
5. WebSocket 协议跨域
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
- 前端代码
<div>user input:<input type="text"></div>
<script src="./socket.io.js"></script>
<script>
var socket = io('http://localhost:8080')
// 连接成功处理
socket.on('connect', function() {
// 监听服务端消息
socket.on('message', function(msg) {
console.log('data from server: ---> ' + msg)
})
// 监听服务端关闭
socket.on('disconnect', function() {
console.log('Server socket has closed.')
})
})
document.getElementsByTagName('input')[0].onblur = function() {
socket.send(this.value)
}
</script>
- 后端代码
var http = require('http')
var socket = require('socket.io')
// 启http服务
var server = http.createServer(function(req, res) {
res.writeHead(200, {
'Content-type': 'text/html'
})
res.end()
})
server.listen('8080')
console.log('Server is running at port 8080...')
// 监听socket连接
socket.listen(server).on('connection', function(client) {
// 接收信息
client.on('message', function(msg) {
client.send('hello:' + msg)
console.log('data from client: ---> ' + msg)
});
// 断开处理
client.on('disconnect', function() {
console.log('Client socket has closed.')
})
})