什么是跨域?
跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
例如:a页面想获取b页面资源,如果a、b页面的协议、域名、端口、子域名不同,所进行的访问行动都是跨域的,而浏览器为了安全问题一般都限制了跨域访问,也就是不允许跨域请求资源。注意:跨域限制访问,其实是浏览器的限制。理解这一点很重要!!!
同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域;
解决方案
网上一大堆解决方案:比如jsonp,cors,iframe等等,这里不展开讨论了,我这里主要从实际项目出发,把其中遇到的跨域问题以及解决方案分享出来,希望能给遇到同样问题的同学一点启发;另外多说一句,跨域绝对不是那么简单的设置响应头就解决所有问题,实际项目遇到的问题可能会很复杂。
问题
本项目是基于vue-cli3.0创建的,使用了axios作为发送http请求的工具,对于axios,也参照网上的做法,封装了一个统一的http工具,里面封装了get和post方法,http.js
// 引入axios
import axios from 'axios';
import store from '@/store/index';
import qs from 'qs';
// 环境的切换
if (process.env.NODE_ENV == 'development') {
axios.defaults.baseURL = 'http://127.0.0.1:3000/';}
else if (process.env.NODE_ENV == 'debug') {
axios.defaults.baseURL = 'https://www.ceshi.com';
}
else if (process.env.NODE_ENV == 'production') {
axios.defaults.baseURL = 'https://www.production.com';
}
axios.defaults.timeout = 10000;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
axios.interceptors.request.use(
config => {
// 每次发送请求之前判断vuex中是否存在token
// 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况
// 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
/*const token = store.state.token;
token && (config.headers.Authorization = token); */
return config;
},
error => {
return Promise.error(error);
});
function checkStatus (response) {
// loading
// 如果http状态码正常,则直接返回数据
if (response && (response.status === 200 || response.status === 304 || response.status === 400)) {
return response.data
// 如果不需要除了data之外的数据,可以直接 return response.data
}
// 异常状态下,把错误信息返回去
return {
status: -404,
msg: '网络异常'
}
}
function checkCode (res) {
// 如果code异常(这里已经包括网络错误,服务器错误,后端抛出的错误),可以弹出一个错误提示,告诉用户
if (res.status === -404) {
alert(res.msg)
}
if (res.data && (!res.data.success)) {
alert(res.data.error_msg)
}
return res
}
export default {
post (url, data) {
return axios({
method: 'post',
url,
data: qs.stringify(data),
timeout: 10000,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded;'
}
}).then(
(response) => {
return checkStatus(response)
}
).then(
(res) => {
return checkCode(res)
}
)
},
get (url, params) {
return axios({
method: 'get',
url,
params, // get 请求时带的参数
timeout: 10000,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}).then(
(response) => {
return checkStatus(response)
}
).then(
(res) => {
return checkCode(res)
}
)
}
}
下面是api.js
import http from './http';
//登录接口
export const login = (param) =>http.post('/test',param);
然后就可以调用了
login(){
var that = this;
login({username:'test'}).then(function(data){
that.saveUserName(data.name);
that.saveToken(data.token);
that.$router.push('home');
})
},
结果可想而知,

ctx.set('Access-Control-Allow-Origin', '*');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With');
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
然后重启服务,再调一次,发现还是报同样的错,这就有点儿颠覆常识了,跟网上说的不一样啊!再仔细看浏览器报错信息,怎么有个options请求报404呢?
经过多番查找,翻阅网上资料,主要是这篇官方文档 developer.mozilla.org/zh-CN/docs/… 总结起来就是:对于简单请求不会触发cors的预检请求,非简单请求会触发预检请求
预检请求:“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
简单请求:若请求满足所有下述条件,则该请求可视为“简单请求”
-
- 使用下列方法之一: GET HEAD POST
-
- Fetch 规范定义了对 CORS 安全的首部字段集合,不得人为设置该集合之外的其他首部字段。该集合为: Accept Accept-Language Content-Language Content-Type (需要注意额外的限制) DPR Downlink Save-Data Viewport-Width Width
- 3.Content-Type 的值仅限于下列三者之一: text/plain multipart/form-data application/x-www-form-urlencoded
- 4.请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
- 5.请求中没有使用 ReadableStream 对象。
也就是要同时满足上面五点条件,这个请求就是简单请求,否则是非简单请求;由于我们在封装http.js的时候在请求头里加了这样一个头:'X-Requested-With': 'XMLHttpRequest',这个头不属于 Accept Accept-Language Content-Language Content-Type DPR Downlink Save-Data Viewport-Width Width这其中的,因此,这个请求属于非简单请求,浏览器会发送预检请求,即options请求,而我们的后台没有这个接口,因此报了一个404
好了,问题找到了,解决起来也就有了思路了,我们有两种方案来解决
- 1.前端解决:改变我们的请求头,使其成为一个简单请求,在这个案例中,去掉'X-Requested-With': 'XMLHttpRequest',再调一次,发现没问题了,问题解决,而且也没有那个options请求了
- 2如果非要在请求头里保留那个字段呢,这个时候就要让后台来解决了,刚刚报错的信息里说的是:没有method为post,/test这个接口,那我们就加上一个接口就行了,但是我们不可能一个一个的加吧。怎么办呢?我们可不可以让所有的options请求都直接响应200呢?这样貌似可以,于是我们来加上这样一段代码
app.use(async (ctx, next)=> {
ctx.set('Access-Control-Allow-Origin', '*');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
if (ctx.method == 'OPTIONS') {
ctx.body = 200;
} else {
await next();
}
});
重启服务,再调一次,发现成功了,而且post请求之前有个options请求

