提到跨域我们就不得不说一下同源 ; 那什么又是同源呢 ?
同源策略 : 是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介
那什么样的情况会涉及到跨域呢 ?
一句话总结 : 只要 请求 协议, 域名, 端口, 任意一个不相同的地址, 都会涉及到跨域问题 ; 跨域也可以说不是一个问题, 是浏览器的一种现象 ;
跨域异常展示
下面我们通过一个简单的案例展示以下跨域异常请求 ;
可以看出没有涉及到跨域的请求可以正常获取到结果, 而涉及到跨域的就不能获取响应结果了 ; 既然出了问题, 就肯定要解决问题 ; 下面就介绍几种跨域的解决方案
jsonp 解决跨域
众所周知在 html 页面中 像 img script iframe 这些带有 src 属性的标签, 后面的引用是不会受到跨域影响的, jsonp的原理简单来说就是利用 html 页面中的 script 的 src 不受同源政策约束, 来跨域获取数据的 ;
// 服务端代码片段 koa
router.get('/getData', async ctx => {
ctx.body = 'alert("jsonp")';
})
<script src="http://localhost:4000/getData"></script>
可以看出我们的script 是完全不受跨域影响的, 甚至可以定义变量, 数据来供前端去使用; 下面看具体实现
// 后端代码 koa
router.get('/getData', async ctx => {
let returnData = {
status: 1,
data: {
list: [
{ name: 'zs', age: 24 },
{ name: 'ls', age: 22 }
]
}
}
// 返回出去一个回调函数, 这里的回调名称要和前端约定
let jsonpStr = `${ctx.query.cb}(${JSON.stringify(returnData)})`
ctx.body = jsonpStr
})
// 前端代码
btns[1].onclick = function () {
$.ajax({
type: 'get',
dataType: 'jsonp',
data: {}, // 如果需要传参在 data 中以 key value 形式传参; 参数会传递 url 中
url: 'http://localhost:4000/getData',
jsonp: 'cb', // 这个回调名称要和后端约定
success: function (res) {
console.log(res.data)
}
})
}
此种方法成功解决了上面我们说的跨域问题, 但是 jsonp 并不是完美的 ;
- 只支持 get 请求, 所有的请求参数只能方法哦 url 后面 即 ? 后面用参数拼接
- 安全问题 : callback参数注入和资源访问授权设置
cors 跨域
CORS(Cross-origin resource sharing),跨域资源共享,是一份浏览器技术的规范,用来避开浏览器的同源策略
简单来说就是解决跨域问题的除了jsonp外的另一种方法;比jsonp更加优雅。
cors 跨域设置 , 主要工作在于后端的修改 ; 下面来看一下
同样在 3000服务器请求 5000服务器 肯定是报错了
大概意思就是请求被 CORS 策略阻止, 缺少一个请求头 ; 可以在后端加上这个请求头试下
app.use(async (ctx, next) => {
/**
* @param 请求头
* @param 允许访问的地址, * 代表所有
*/
ctx.set('Access-Control-Allow-Origin', 'http://localhost:3000');
await next();
});
漂亮, 解决了跨域问题, 真的这么简单吗 ; 此时我们再客户端加上请求头来再访问一次 ;
let xhr = new XMLHttpRequest()
xhr.open('get', 'http://localhost:5000/getData', true)
xhr.setRequestHeader('Content-Type', 'application/json;charset=utf8')
xhr.onload = function () {
let res = JSON.parse(xhr.responseText)
console.log(res)
}
xhr.send()
大概意思发送来的请求没有通过访问控制检查 ; 检查我们发送至后端的请求, 发现请求方式为 OPTIONS 原因就在这里了 ;
预检请求
“需预检的请求”要求必须首先使用
OPTIONS方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响什么样的请求 是预检请求呢 ? 有预检请求, 肯定就会有简单请求吧 ?
简单请求
GET POST HEAD 以上三种请求只要携带了 content-type 就是预检请求 text/plain multipart/form-data application/x-www-form-urlencoded预检请求
PUT DELETE CONNECT OPTIONS TRACE PATCH
既然已经知道了问题原因, 那就直接上解决方案吧 ;
// 判断是不是预检请求 如果是的话, 随便给点东西就行
if (ctx.method == 'OPTIONS') {
ctx.body = '';
} else {
await next();
}
此时再看, 同时发送的两个请求, 其中预检请求已经解决了 但是还有一个不行的; 好现在我们总结一下还有哪些问题没有解决 ; 下面上解决方案 ;
- 带有请求头的请求没有给出响应 ;
- 每次 request 都会给出两个响应 ;
app.use(async (ctx, next) => {
// 允许客户端携带的请求头, 如果需要自定义, 可自行添加
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With', 'your-Headers');
if (ctx.method == 'OPTIONS') {
// 用来指定本次预检请求的有效期, 在此期间不用发出另一条预检请求。
ctx.set(ctx.set('Access-Control-Max-Age', 3600 * 24)); // 单位 s
ctx.body = '';
} else {
await next();
}
});
目前为止我们已经解决 get 方法的跨域问题 ; 为什么说是 get 呢, 不要急 ; 这里直接附 完整代码 吧 , 大家看注释;
app.use(async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', 'http://localhost:3000');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With', 'your-Headers');
// 允许的请求方式
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
if (ctx.method == 'OPTIONS') {
ctx.set(ctx.set('Access-Control-Max-Age', 3600 * 24)); // 单位 s
ctx.body = '';
} else {
await next();
}
});
后端代理方案解决跨域
跨域是浏览器规范 , 通过服务器去发送请求 , 绕过前端浏览器 , 也能解决浏览器限制 ;
koa-server-http-proxy 中间件实现代理
// 此种方式是当前服务器配置, 从当期服务器代理到其他服务器
const koaServerHttpProxy = require('koa-server-http-proxy');
app.use(koaServerHttpProxy({
target: 'http://localhost:5000', // 目标服务器
pathRewrite: { '^/api': '' }
}));
axios 实现请求转发
// 当前服务
router.get('/axios', async ctx => {
let { data } = await axios.get('http://localhost:5000/axios')
ctx.body = data
})
// 目标服务
router.get('/axios', async ctx => {
ctx.body = {
status: 200,
info: 'axios 转发请求成功...'
}
})
Vue config 解决跨域
// 本地项目如果直接访问其他域名的接口, 可以预见到肯定是会出现跨域问题的
created() {
axios.post('http://api.diopoo.com/v1/api/index/banner', {
app: 'index',
position: '1'
}).then(res => this.res = res)
},
Vue 在开发环境中提供了一种跨域方式 , 我们需要在项目的根目录创建
vue.config.js文件进行配置
/* 跨域页面 */
// 资源都代理到 /api 这里 所以用他访问
axios.post('/api/index/banner', {
app: 'index',
position: '1'
}).then(res => this.res = res)
/* vue.config.js */
devServer: {
proxy: {
'/api': {
target: 'http://api.diopoo.com/v1/api', // 代理的目标地址
ws: true, // 求大佬评论区解释这个干吗的
changeOrigin: true, // 允许跨域
pathRewrite: {
'^/api': '' // 把多余出来的 /api 替换成空
}
}
}
}
webpack 跨域
vue.config.js中内置了 webpack 的一些配置 , 既然它可以 那么 webpack 一定也是可行的 , 下面附上 webpack 基础配置 , 注意测试 webpack 一定要自己启动一个服务器 ; 同样也是只适用于开发环境的其他他俩的配置是差不多的 , 大家可以感受下 , 重点看 proxy 和 index 中的代码
/* webpack.config.js */
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: 'main.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'main.js'
},
mode: 'development',
devServer: {
proxy: {
'/api': {
target: 'http://api.diopoo.com/v1/api', // 代理地址
changeOrigin: true, // 允许跨域
pathRewrite: {
'^/api': '' // 替换
}
}
}
},
plugins: [
new HtmlWebpackPlugin()
]
}
/* index.js */
import axios from 'axios'
// 同样 /api 请求
axios.post('/api/index/banner', {
app: 'index',
position: '1'
}).then(res => console.log(res))
本文使用 mdnice 排版