跨域的前世今生和最佳实践

本文作者为口袋前端团队—朱未

什么是跨域

出于安全因素考虑,浏览器有同源策略限制,即限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。如果协议、端口和主机只要有一个不同,则认为跨域。当然,跨域并不一定是浏览器限制了发起跨站请求,也可能是跨站请求可以正常发起,但是返回结果被浏览器拦截了。

跨域的分类

主域相同的跨域和完全不同源的跨域。

主域相同的跨域

这种情况通常会出现在iframe标签中,解决的方法是使用document.domain,这种方式只适合不同子域框架间的交互,及主域必须相同的不同源。

document.domain = 'targetDomain'

完全不同源的跨域

方法有很多,比如location.hash, window.name, window.postMessage,这些方法不太常用,具体可以看这里

Ajax跨域请求

JSONP

利用了script标签可以跨域请求资源的特性。这个方法以前用的比较多,因为兼容性好,但是缺点也很明显,只能发get请求。

WebSocket

这个协议不实行同源政策,只要服务器支持,就可以通过它进行跨域。

CORS

这是目前最通用的跨域方式。

由于前后端分离开发,跨域问题经常遇到,为了使开发便捷,隶属于 W3C 的 Web 工作组推荐了一种新的机制,即跨域资源共享(Cross-origin Resource Sharing)

优点:
- 兼容性良好
- 只需服务端添加几行代码,前端不需要修改

浏览器兼容性

跨域资源共享标准允许在下列场景中使用跨域 HTTP 请求:
- 由XMLHttpRequestFetch发起的跨域 HTTP 请求
- Web 字体( CSS 中通过@font-face使用跨域字体资源)
- WebGL 贴图
- 使用drawImageImages/video会面绘制到canvas
- 样式表
- 脚本

两种请求

CORS 的请求分两种,这也是浏览器为了安全做的一些处理,不同情况下浏览器执行的操作也是不一样的,主要分为两种请求,简单请求复杂请求

简单请求

判断简单请求有两个条件,只要同时满足就是简单请求。

1. 请求是下列之一:
HEAD
GET
POST

2. HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

对于简单的跨域请求,浏览器会自动在请求的头信息加上Origin字段,表示本次请求来自哪个源(协议 + 域名 + 端口),服务端会获取到这个值,然后判断是否同意这次请求并返回。

解决方案

因此只要服务端在header中设置Access-Control-Allow-Origin的值为*或是origin中指明的站点即可。

复杂请求

如果不是简单请求,那就是复杂请求,比如请求的方法是 PUT 或者 DELETE ,比如Content-Type字段的类型是application/json,比如设置了自定义头信息。

称之为复杂请求,主要是因为他比简单请求多了个预检请求

预检请求

非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为预检请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

预检请求用的请求方法是 OPTIONS ,表示这个请求是用来询问的。头信息里面,关键字段是 Origin ,表示请求来自哪个源。除了origin字段,还有两个字段非常重要:Access-Control-Request-MethodAccess-Control-Request-Headers。分别表示允许的请求方法和请求头。

解决方案

因此对于复杂请求的解决方案就是在响应头中设置来自浏览器的请求方法和请求头。

例如前端发起的请求如下:

$.post({
  url: 'http://192.168.1.130:8082/UserWebServer/sms/sendPhoneCode',
  data: {
    type: 'reset',
    phone: $('#tele').val(),
    captcha: $('#pic').val(),
  },
  success: function (res) {
    console.log(res);
    if (res.status === 1007) {
      toast(res.desc)
      $('#pic').val('');
      picVerifyCode()
    } else if (res.status === 1012) {
      toast(res.desc)
      picVerifyCode()
    } else if (res.status === 1000) {
      toast('验证码发送成功!')
      alert(res.desc)
    }
  },
  error: function (e) {
    toast('网络错误,请稍后再试!')
  },
  beforeSend: function (xhr) {
    xhr.setRequestHeader("platform", "web");
    xhr.setRequestHeader("imei", result);
  },
})

java为例,后端的代码为

httpServletResponse.setHeader("Access-Control-Allow-Origin","*");
httpServletResponse.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS");
httpServletResponse.setHeader("Access-Control-Allow-Headers","imei,platform");

除此之外,还有两个比较重要的属性,分别是Access-Control-Max-AgeAccess-Control-Allow-Credentials

Access-Control-Max-Age指定了preflight请求的结果能被缓存多久,单位为秒。在此期间,不用发出另一条预检请求。

Access-Control-Allow-Credentials指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容。当用在对preflight预检测请求的响应中时,它指定了实际的请求是否可以使用credentials。请注意:简单 GET 请求不会被预检;如果对此类请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页。并且,对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin的值为*,必须设置为具体站点。

目前利用 CORS 解决跨域是认同度较高的解决跨域问题的最佳实践,关于 CORS 更多的信息查看这里

纯前端解决跨域问题

其实只是用nodejs来解决问题,还是服务端的知识。比如 VUE 的脚手架工具就提供了跨域的解决方法。

// config/index.js
module.exports = {
  // ...
  dev: {
    proxyTable: {
      // proxy all requests starting with /api to jsonplaceholder
      '/api': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }
}

上面的例子会将/api/posts/1代理到http://jsonplaceholder.typicode.com/posts/1

关于 VUE 脚手架配置跨域的详细内容看这里,VUE 脚手架其实是用到了http-proxy-middleware,具体可以查看它的github

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值