跨域是个老生常谈的问题,都谈臭了,我在实际工作中,其实遇到不多。现在基本都是前后端分离开发,开发阶段(一般是本地起个服务),用 webpack
代理就可以解决跨域的问题,实在不行,用非安全模式的 chrome
也可以(我原先就这么干过);部署阶段,我们前端需要做的也不多,我们只需打个包出来就可以了(顶多就是打包前的配置,路由模式设置等)。但是,面试必问啊,而且一直对 cors
这种方案似懂非懂,所以就用代码撸一遍咯~
跨域完全就是浏览器搞得鬼,由于浏览器同源策略的限制,协议、域名、端口号只要有一个不同,就是不同源。
本文记录的都是自己敲出来并验证过的,前后端都是本地起的服务,前端 vue-cli3
搭的 vue
工程,封装 axios
请求,后端用的 express
+ mysql
。废话不多说,直接上代码:
1. webpack 代理,主要用于开发阶段
// vue.config.js
...
devServer: {
host: '0.0.0.0',
port: 8080,
open: true,
overlay: {
warning: false,
errors: true
},
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
secure: false,
pathRewrite: {'^/api': ''}
}
}
}
...
代码中 target
就是实际提供接口的地址,本文中的接口完整都是这样 http://localhost:3001/user/login
, http://localhost:3001/user/get_user_info
。/api
是为了识别哪些请求需要代理,否则 js
等静态资源请求也会被代理的。前端发起一个请求时,如登录 http://localhost:8080/api/user/login
(下文有说明),就会被转发至 http://localhost:3001/api/user/login
这个接口中,但是我们的接口是这样子的 http://localhost:3001/user/login
,没有 api
, pathRewrite
的作用就是把 api
去掉的。
// request.js
...
axios.defaults.baseURL = '/api' // 默认为 '/', 即 http://localhost:8080/
// 设置 post 请求头
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.defaults.timeout = 5000
...
上述的代码中,baseURL
加个 api
,是为了让 webpack
能够识别哪些请求需要代理。
具体的每个接口
// api/user.js
import {get, post} from '../utils/request'
export const login = params => post('/user/login', params)
export const getUserInfo = params => post('/user/get_user_info', params)
export const getList = params => get('/user/get_list', params)
...
这样,开发阶段就能愉快地开发了
但是面试老师问时,感觉还是没答到重点,嗯,那就看看 cors
吧
2. cors
cors
全称是跨域资源共享,具体可以看 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS。cors
分为简单请求和非简单请求,具体的区别网上一大堆,本文以简单请求为例。还是以之前登陆的接口为例,这回就关掉 webpack
来代理了,直接 axios
将服务地址写死:
// request.js
...
axios.defaults.baseURL = 'http://localhost:3001'
...
重启 webpack
,走一波,毫无意外,浏览器有错误
但是,请求结果结果还是 200,响应数据也可以看到
之前说过,这个和后端没毛线关系,就是浏览器搞得鬼,浏览器发现响应头里面没有 Access-Control-Allow-Origin
这个字段,就知道这个请求有问题,就抛出个错误,这个错误被 XMLHttpRequest
的 onerror
回调函数捕获。那怎么解决呢,这其实就要用到 cors 了,前端几乎不需要做什么,只需后端改改就可以了。
安装:npm install --save cors
// app.js
...
const cors = require('cors')
app.use(cors())
...
这样就所有的源就可以请求了
果然,响应头里面就有 Access-Control-Allow-Origin
这个字段了,当然也可以指定某些域才能请求
// app.js
...
const cors = require('cors')
app.use(cors({origin: 'http://localhost:8080'}))
...
浏览器就是根据这个字段来判断是不是存在跨域,这样跨域就解决了。但是,还有一个需要提一下,嗯,cookie。前一篇文章中 jwt 存在哪 说到,登录成功后,jwt
保存在 cookie
中了,以后每个请求都会带上 cookie
的,但是,用了 cors
之后,login
接口正常了,get_user_info
这个接口报错了:
403 Forbidden
其实就是这个请求没有携带 cookie
exports.get_user_info = function (req, res, next) {
const token = req.cookies.token
if (token) {jwt.verify(token, SECRET, async (error, decoded) => {if (error) {
// token 过期
return res.status(401).send({
success: false,
message: 'token 已过期,请重新登录'
})
} else {const userInfo = await userModel.getUserById(decoded.id)
return res.send({
code: 100,
message: '返回成功',
data: {userInfo: userInfo[0]
}
})
}
})
} else {
// 没有拿到 token 返回错误
return res.status(403).send({
success: false,
message: '没有找到 token'
})
}
}
顺便说句题外话,之前一直没有搞明白,浏览器接收的状态码(200,304, 401,403, 500 等)到底是谁给出来的,我猜应该是 web
容器(nginx
, apache
)或者 nodejs
给的。
所以,cors
解决跨域还得配置请求时可以发送 cookie
// app.js
...
const cors = require('cors')
app.use(cors({
origin: 'http://localhost:8080',
credentials: true
}))
...
此外,前端也要稍微改一下
// request.js
...
// axios.defaults.withCredentials = true // 默认为 false,表示跨域请求时是否需要使用凭证,如 cookie
...
这样就可以愉快地请求啦
3. nginx
反向代理
// TODO