简言

跨域是前端开发者不可避免的概念之一,也是面试中会经常出现的话题。在平时的开发工作中我也会遇到跨域的问题,如果你不能很好的了解并把握它,会极大的影响你的工作效率,也会消耗你大量的工夫来翻阅材料,一步步解决对应的问题点。同时,这篇文章也是我在屡次解决同样问题后,意识到须要总结一下并且留下文档,以便前面再遇到问题能够疾速解决。如果你也遇到跨域的问题,那么就请持续往下浏览,我会带你浏览整篇文章,来一步步地理解跨域并且把握解决跨域问题的办法。好了话不多说,让咱们开始吧。
本篇文章相干代码

了解跨域

在开发我的项目的时候,咱们必定会须要拜访后盾服务的资源。如果用XMLHttpRequest/axios/fetch间接拜访后盾资源(假如后盾服务没有配置cors的状况下),浏览器会出于平安起因拦挡掉该响应,那么此时咱们的接口拜访就失败了,对应的数据也拿不到,浏览器控制台会打印出相似跨域的谬误(Access to XMLHttpRequest at 'http://localhost:3000/cors' from origin 'http://localhost:3001' has been blocked by CORS policy)。

  1. 那什么是跨域呢?简略来讲,如果两个服务的协定不同(http/https),或者域名不同(www.a.com/www.b.com),或者端口不同(www.a.com:80/www.a.com:81),此时想要拜访另一个服务,就会产生跨域。
  2. 那为什么要产生跨域?跨域自身是浏览器施行的,基于平安起因,浏览器会限度来自脚本初始化的跨域http申请。比方浏览器中常见的XMLHttpRequest和fetch,他们都听从同源准则。

解决跨域

大略讲述完跨域,可能你会想,既然浏览器会拦挡跨域的响应,那么岂不是咱们都拿不到跨域服务的数据?然而事实中很多我的项目都是跨域,如同都失常运行的,这是怎么回事?那么这里就须要理解跨域资源共享技术了。

跨域资源共享,是一种基于http头信息的机制。它容许服务器批示,浏览器应该容许加载该服务器的资源。即通过该机制,服务器通知浏览器,你应该加载我发送的响应,而不是拦挡掉,即便咱们当初是跨域的。

理解了跨域资源共享,咱们就能够针对性的解决各种波及跨域的问题。以下我列举了几种跨域并且通过代码来复现跨域并且解决该跨域问题。

简略申请

简略申请,即不会登程预检的申请。它须要满足上面的条件:

  1. http办法为GET/HEAD/POST之一
  2. 除了被用户代理主动设定的头信息(比方ConnectionUser-Agent等),仅可再蕴含AcceptAccept-LanguageContent-LanguageContent-Type。请留神上面Content-Type额定的限度
  3. Content-Type只容许三种值:Application/x-www-form-urlencodedmultipart/form-datatext/plain

    // // backend/index.htmlconst xhr = new XMLHttpRequest()xhr.open("get", "http://localhost:3000/cors", true)xhr.onreadystatechange = function() {  if (xhr.readyState === XMLHttpRequest.DONE) { const status = xhr.status; if (status === 0 ||  (status >= 200 && status < 400)) {   console.log(xhr.responseText)   return } // an error   }}xhr.send()
    // backend/app.jsif (req.url === "/cors") {  res.end("hello cors");  return;}

    那么此时,咱们就会遇到第一个跨域问题。控制台通知咱们(Access to XMLHttpRequest at 'http://localhost:3000/cors' from origin 'http://localhost:3001' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.)所以咱们在后盾服务减少响应的头信息Access-Control-Allow-Origin来解决。

    // backend/app.jsres.setHeader("Access-Control-Allow-Origin", "*")if (req.url === "/cors") {  res.end("hello cors");  return;}

    问题迎刃而解,这是一个解决简略申请的跨域问题。

预检申请

咱们把上述的前端申请的头信息改变,不合乎简略申请的范畴,比方http办法为put/delete,或者Content-Type的值为application/json等等,咱们发现浏览器又报错了(Access to XMLHttpRequest at 'http://localhost:3000/cors' from origin 'http://localhost:3001' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.)

// frontend/index.htmlconst xhr = new XMLHttpRequest()xhr.open("get", "http://localhost:3000/cors", true)xhr.setRequestHeader("Content-Type", "application/json") // ++xhr.onreadystatechange = function() {  if (xhr.readyState === XMLHttpRequest.DONE) {    const status = xhr.status;    if (status === 0 ||  (status >= 200 && status < 400)) {      console.log(xhr.responseText)      return    }    // an error   }}xhr.send()

那什么是预检申请?不像咱们上述的简略申请,对预检申请来说,浏览器首先须要用OPTIONS办法来发送一个http申请到另一个域,来决定实在申请是否能够平安发送。上述代码就造成了预检未通过,所以浏览器在OPTIONS申请后,收回了实在的申请,然而拦挡掉了响应,所以收回了上述的谬误。那么咱们要怎么解决呢?

首先,咱们发现OPTIONS申请有两个之前没有的头信息

  • Access-Control-Request-Headers: content-type
  • Access-Control-Request-Method: GET

那么咱们能够在后盾服务对应设置响应头来解决该跨域问题

// backend/app.jsres.setHeader("Access-Control-Allow-Origin", "*")res.setHeader("Access-Control-Allow-Methods", "GET")res.setHeader("Access-Control-Allow-Headers", "Content-Type")if (req.url === "/cors") {  res.end("hello cors");  return;}

此时咱们再看控制台,报错音讯了,申请也胜利了。

有受权的申请

很多时候咱们的后盾服务是有权限相干的,比方不同用户/租户等等,对雷同的后盾接口申请会返回不同的数据,那么咱们会用到HTTP身份验证信息。在跨域XMLHttpRequest/feth申请中,浏览器默认不会发送身份验证信息。咱们能够设定该标记

// frontend/index.htmlconst xhr = new XMLHttpRequest()xhr.open("get", "http://localhost:3000/cors", true)xhr.withCredentials = true // ++xhr.setRequestHeader("Content-Type", "application/json")xhr.onreadystatechange = function() {  if (xhr.readyState === XMLHttpRequest.DONE) {    const status = xhr.status;    if (status === 0 ||  (status >= 200 && status < 400)) {      console.log(xhr.responseText)      return    }    // an error   }}xhr.send()

咱们会发现,此时浏览器又报错了(Access to XMLHttpRequest at 'http://localhost:3000/cors' from origin 'http://localhost:3001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.)
那么此时咱们把谬误中提出的Access-Control-Allow-Origin,值改为咱们对应的前端服务地址试下

// backend/app.jsres.setHeader("Access-Control-Allow-Origin", "http://localhost:3001") // changedres.setHeader("Access-Control-Allow-Methods", "GET")res.setHeader("Access-Control-Allow-Headers", "Content-Type")

此时又报错了(Access to XMLHttpRequest at 'http://localhost:3000/cors' from origin 'http://localhost:3001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.)
那咱们接着改Access-Control-Allow-Credentials的值来看下

// backend/app.jsres.setHeader("Access-Control-Allow-Origin", "http://localhost:3001")res.setHeader("Access-Control-Allow-Credentials", "true") // ++res.setHeader("Access-Control-Allow-Methods", "GET")res.setHeader("Access-Control-Allow-Headers", "Content-Type")

功败垂成,咱们又如愿获取到了跨域服务的响应。

总结

读完文章,咱们理解了很多跨域的常识,并且把握了常见的跨域谬误的解决办法。本文是基于MDN-cors,通过简略代码来复现跨域谬误并且逐渐解决问题。如果有什么意见或者发现本文的谬误,请分割TWITTER、<shangfxh@gmail.com>、QQ(1010454733)。
留神:本文的nodejs代码比较简单,并没有任何业务逻辑/平安相干,请勿放到正式环境中应用