乐趣区

这样应该可以回答好跨域了吧

跨域是个老生常谈的问题,都谈臭了,我在实际工作中,其实遇到不多。现在基本都是前后端分离开发,开发阶段(一般是本地起个服务),用 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 这个字段,就知道这个请求有问题,就抛出个错误,这个错误被 XMLHttpRequestonerror 回调函数捕获。那怎么解决呢,这其实就要用到 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

退出移动版