注: 文章摘自 写 Bug - 思否
什么是跨域
出于浏览器的同源策略限制. 同源策略 (Sameoriginpolicy) 是一种约定, 它是浏览器最核心也最基本的安全功能, 如果缺少了同源策略, 则浏览器的正常功能可能都会受到影响. 可以说 web 是构建在同源策略基础之上的, 浏览器只是针对同源策略的一种实现. 同源策略会阻止一个域的 JavaScript 脚本和另外一个域的内容进行交互. 所谓同源 (即指在同一个域) 就是两个页面具有相同的协议 (protocol), 主机(host) 和端口号(port)
解决方式
一. 设置代理
例: 在 vue.config.JS 文件中添加如下配置
- const webpack = require('webpack');
- module.exports = {
- devServer: {
- open: true, // 在 DevServer 启动, 且第一次构建完时自动打开网页
- port: 8080, // 端口号
- proxy: { // 设置代理
- '/api': { // 网关地址
- target: 'https://test.com', // 接口的域名
- ws: true, // 启用 websocket
- secure: false, // 如果是 https 协议, 需要配置这个参数
- // 开启代理: 在本地会创建一个虚拟服务端, 然后发送请求的数据, 并同时接收请求的数据,
- // 这样服务端和服务端进行数据的交互就不会有跨域问题
- changOrigin: true, // 跨域需开启
- pathRewrite: { // 重写地址
- '^/api': ''// 将前缀'/api'转为'/'
- }
- }
- }
- }
- }
二. JSONP
注: 由于本质上 script 加载资源的方式是 GET, 所以 JSONP 只能用于 GET 请求
在 html 标签里, 一些标签比如 script,img 这样的获取资源的标签是没有跨域限制的, 利用这一点, 我们可以这样干:
后端写个小接口
- // 处理成功失败返回格式的工具
- const {successBody} = require('../utli')
- class CrossDomain {
- static async JSONP (ctx) {
- // 前端传过来的参数
- const query = ctx.request.query
- // 设置一个 cookies
- ctx.cookies.set('tokenId', '1')
- // query.cb 是前后端约定的方法名字, 其实就是后端返回一个直接执行的方法给前端, 由于前端是用 script 标签发起的请求, 所以返回了这个方法后相当于立马执行, 并且把要返回的数据放在方法的参数里.
- ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}, 'success'))})`
- }
- }
- module.exports = CrossDomain;
前端这样写
- <!DOCTYPE HTML>
- <HTML>
- <head>
- <meta charset="utf-8">
- </head>
- <body>
- <script type='text/javascript'>
- // 后端返回直接执行的方法, 相当于执行这个方法, 由于后端把返回的数据放在方法的参数里, 所以这里能拿到 res.
- Windows.jsonpCb = function(res) {
- console.log(res)
- }
- </script>
- <script src='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb'
- type='text/javascript'>
- </script>
- </body>
- </HTML>
简单封装一下前端这个套路
- const request = ({url, data}) => {
- return new Promise((resolve, reject) => {
- // 处理传参成 xx=yy&aa=bb 的形式
- const handleData = (data) => {
- const keys = Object.keys(data)
- const keysLen = keys.length
- return keys.reduce((pre, cur, index) => {
- const value = data[cur]
- const flag = index !== keysLen - 1 ? '&' : ''
- return `${pre}${cur}=${value}${flag}`
- }, '')
- }
- // 动态创建 script 标签
- const script = document.createElement('script')
- // 接口返回的数据获取
- Windows.jsonpCb = (res) => {
- document.body.removeChild(script)
- delete Windows.jsonpCb
- resolve(res)
- }
- script.src = `${url}?${handleData(data)}&cb=jsonpCb`
- document.body.appendChild(script)
- })
- }
- // 使用方式
- request({
- url: 'http://localhost:9871/api/jsonp',
- data: {
- // 传参
- msg: 'helloJsonp'
- }
- }).then(res => {
- console.log(res)
- });
三. 空 iframe 加 form
后端写个小接口
- // 处理成功失败返回格式的工具
- const {successBody} = require('../utli')
- class CrossDomain {
- static async iframePost (ctx) {
- let postData = ctx.request.body
- console.log(postData)
- ctx.body = successBody({postData: postData}, 'success')
- }
- }
- module.exports = CrossDomain;
前端这样写
- const requestPost = ({url, data}) => {
- // 首先创建一个用来发送数据的 iframe.
- const iframe = document.createElement('iframe')
- iframe.name = 'iframePost'
- iframe.style.display = 'none'
- document.body.appendChild(iframe)
- const form = document.createElement('form')
- const node = document.createElement('input')
- // 注册 iframe 的 load 事件处理程序, 如果你需要在响应返回时执行一些操作的话.
- iframe.addEventListener('load', function () {
- console.log('post success')
- })
- form.action = url
- // 在指定的 iframe 中执行 form
- form.target = iframe.name
- form.method = 'post'
- for (let name in data) {
- node.name = name
- node.value = data[name].toString()
- form.appendChild(node.cloneNode())
- }
- // 表单元素需要添加到主文档中.
- form.style.display = 'none'
- document.body.appendChild(form)
- form.submit()
- // 表单提交后, 就可以删除这个表单, 不影响下次的数据发送.
- document.body.removeChild(form)
- }
- // 使用方式
- requestPost({
- url: 'http://localhost:9871/api/iframePost',
- data: {
- msg: 'helloIframePost'
- }
- });
四. CORS
CORS 是一个 W3C 标准, 全称是 "跨域资源共享"(Cross-origin resource sharing)跨域资源共享 CORS 详解. 看名字就知道这是处理跨域问题的标准做法. CORS 有两种请求, 简单请求和非简单请求.
只要同时满足以下两大条件, 就属于简单请求.
请求方法是以下三种方法之一:
HEAD
GET
POST
HTTP 的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
Content-Type: 只限于三个值 application/x-www-form-urlencoded,multipart/form-data,text/plain
后端
- // 处理成功失败返回格式的工具
- const {successBody} = require('../utli')
- class CrossDomain {
- static async cors (ctx) {
- const query = ctx.request.query
- //* 时 cookie 不会在 http 请求中带上
- ctx.set('Access-Control-Allow-Origin', '*')
- ctx.cookies.set('tokenId', '2')
- ctx.body = successBody({msg: query.msg}, 'success')
- }
- }
- module.exports = CrossDomain;
前端什么也不用干, 就是正常发请求就可以, 如果需要带 cookie 的话, 前后端都要设置一下
- fetch(`http://localhost:9871/api/cors?msg=helloCors`, {
- // 需要带上 cookie
- credentials: 'include',
- // 这里添加额外的 headers 来触发非简单请求
- headers: {
- 't': 'extra headers'
- }
- }).then(res => {
- console.log(res)
- });
来源: http://www.jianshu.com/p/674e560dca28