遇到跨域不着急,三种方法帮助你~

806 阅读5分钟

前言

  • 不管是 ajax 请求、canvas 的渲染等,都有可能牵涉一个问题 - 跨域
  • 跨域是因为受到了浏览器同源策略的影响
  • 我们也经常遇到跨域的问题,这让我们很多功能的实现上都产生了一定影响,我们下面就来看看,如何解决跨域的问题
  • 完整相册 Demo - 相册

前后端交互问题 - 跨域

浏览器同源策略

  • 同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源
  • 同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收
  • 源 :协议、域名和端口号

不受同源策略影响的资源引入

  • <script></script><img/><link></link><iframe></iframe> - src

方案

jsonp

  • jsonp - JSON width Padding
  • jsonp 的原理 - 通过 <script></script> 不受同源策略影响的特性实现跨域

简单使用

  • 后端 node - koa
router.get('/getAjax', ctx => {
    // 直接返还浏览器可执行的 js 代码
    ctx.body = 'var a = 123';
});
  • 前端
<script src="http://127.0.0.1:1000/getAjax"></script>
<script>
    console.log(a); // 123
</script>
  • 这么做有一些新的问题产生
    • 变量名污染
    • 交互是为了通信,我把数据传给你,你把数据传回给我,这么做就只是 js 引入,没有任何意义

动态创建 script 实现请求

  • 我们根据之前的 简单使用 出现的问题,对 jsonp 的 script 进行修改
  1. 使用 paramsquery 进行 客户端->浏览器 的数据传输
  2. 根据需求动态创建 script
  3. 异步问题 - 请求需要时间,那么后面会有可能使得所需数据没加载进来但你却使用了导致报错
const btn = document.querySelector('button');
btn.onclick = () => {
    const jsonp = document.createElement('script');
    jsonp.src = `http://127.0.0.1:3000/getAjax`;
    document.head.appendChild(jsonp);
    // 避免异步问题,等 onload 之后再执行
    jsonp.onload = () => {
        console.log(a);
    }
}
  1. onload 这种方式感觉没什么问题,但看着挺蠢的,所以我们可以利用回调再改一下,把回调的方法名传到后台,再由后端返回执行js
  • 后端 node - koa
router.get('/getAjax', ctx => {
    const { cb, name } = ctx.query;
    ctx.body = `${cb}({
        name: '${name}',
        age: 20
    })`;
});
  • 前端
const btn = document.querySelector('button');
btn.onclick = () => {
    const jsonp = document.createElement('script');
    jsonp.src = `http://127.0.0.1:3000/getAjax?name=张三&cb=cbFn`;
    document.head.appendChild(jsonp);
}

function cbFn(options) {
    console.log(options);
}

典型例子 - 百度搜索

  • 百度接口
https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=hello&cb=succFn
  • wd - 搜索关键字
  • cb - 回调

jsonp存在的问题

  • 只能是 get 请求
  • 安全性问题

CORS跨域设置

  • CORS(Cross-origin resource sharing),跨域资源共享,是一份浏览器技术的规范,用来避开浏览器的同源策略
  • 该设置需要后端配合
  • 实际上就是后端返回数据时携带一个特殊的头信息

Access-Control-Allow-Origin

  • 当我们的浏览器通过 ajax 发送了一个请求的时候,如果该请求不同源,那么浏览器会去看一下请求头信息里面是否存在一个 Access-Control-Allow-Origin 字段,且当前的域是否在该字段的值内,
  • 如果为真,则表示该数据是可信任的,就接收响应数据
  • 否则拒绝接收
  • 后端 - node - koa
ctx.set('Access-Control-Allow-Origin', 'http://127.0.0.1:8080'); // 表示只有 http://127.0.0.1:8080 可以访问
ctx.set('Access-Control-Allow-Origin', '*'); // * 为通配符,表示任何域名都可以访问

CORS 头信息设置

  • CORS请求时,XMLHttpRequest 对象的 getResponseHeader() 只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma
  • 如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定
  • 我们还需要通过两个特殊头信息设置
    1. Access-Control-Allow-Headers - 允许requset设置的头部
    2. Access-Control-Expose-Headers - 允许客户端获取的头部key
  • 后端 - node - koa
ctx.set({
    'Access-Control-Allow-Origin': 'http://127.0.0.1:8080',
    'Access-Control-Allow-Headers': 'Authorization',
    'Access-Control-Expose-Headers': 'Authorization'
});

预检请求

  • 在跨站点请求的时候,即使服务器设置了 Access-Control-Allow-Origin ,如非简单请求,也会请求失败,其原因是因为在正式请求前,会有一个预检请求

  • 需预检的请求要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求

  • 预检请求的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响

  • 简单请求 - 满足条件:

    • method

      1. GET
      2. POST
      3. HEAD
    • 除了被用户代理自动设置的头信息(例如 ConnectionUser-Agent和在 Fetch 规范中定义为 禁用首部名称 的其他头信息,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为:

      1. Accept
      2. Accept-Language
      3. Content-Language
      4. Content-Type(需要注意额外的限制)
      5. DPR
      6. Downlink
      7. Save-Data
      8. Viewport-Width
      9. Width
    • Content-Type 的值仅限于下列三者之一:

      1. text/plain
      2. multipart/form-data
      3. application/x-www-form-urlencoded
  • 解决方案 - 后端允许预检通过

  • 后端 - node - koa

if(ctx.method === 'OPTIONS') {
    ctx.set('Access-Control-Request-Method', 'POST');
    return ctx.body = ''
}

后端代理

  • 后端 即能接电话(响应-提供服务),还能打电话(发送请求)

  • 跨域问题是由浏览器的同源策略这个安全机制引起的,我们就可以通过服务器请求数据,不由浏览器直接请求数据,解决跨域问题

  • 即由同源服务器作为代理,访问其他服务器:浏览器 -> 同源服务器 -> 非同源服务器 -> 同源服务器 -> 浏览器

  • node原生

let data;
const options = {
    protocol: 'http:',
    hostname: '127.0.0.1',
    port: 3000,
    path: url.replace(/^\/api/, ''),
    method,
    headers,
}
const req = http.request(options, res => {
    res.on('data', chunk => {
        data += chunk.toString();
        console.log(`BODY: ${chunk}`);
    })
    res.on('end', () => {
        res.write(data);
        console.log('完成');
    })
})
req.on('error', (e) => {
    console.error(`problem with request: ${e.message}`);
});
req.end();
  • koa-server-http-proxy
app.use(proxy('/api', {
    target: 'http://127.0.0.1:3000',
    pathRewrite: {
        '^/api': ''
    }
}));

代理方式