我用2000字把Cors跨域讲清楚了!

1,041 阅读6分钟

小知识,大挑战!本文正在参与“   程序员必备小知识   ”创作活动
本文同时参与 「掘力星计划」   ,赢取创作大礼包,挑战创作激励金

Cors跨域原理

Cors全称Cross-Origin Resource Sharing ,是官方推荐的跨域解决方案。

Cors跨域的原理实际上是浏览器与后端服务器通过一些 HTTP 协议头来做一些约定和限制

实现Cors通信的关键主要是在后端,只要后端服务器实现了Cors接口,就可以跨域通信。

未跨域前,本页面向其他域请求资源时,控制台会报错👇

XMLHttpRequest(xhr)不能请求http://127.0.0.1:8080/user/selectUser,请求资源的响应头中没有设置Access-Control-Allow-Origin,所以Origin:http://localhost:8000是不允许跨域请求的。

未使用Cors跨域前

在后端响应跨域请求时,比对前端的请求头Origin和已经设置好的响应头Access-Control-Allow-Origin👇

后端设置 Access-Control-Allow-Origin 就是指定了哪些域名可以跨域向后端请求资源,前端浏览器通过 Response Headers 中的 Access-Control-Allow-Origin 就可以知道能不能把数据吐出来。

在后端设置跨域拦截器

如果Origin指定的域名在Access-Control-Allow-Origin许可范围内,后端就会给前端返回Access-Control-Allow-Origin信息,告诉你“跨域成功啦!”,后端可以正常向前端传递数据了✌。

设置Cors后

以上就是允许一个简单的跨域请求的做法,只需要后端设置响应头Access-Control-Allow-Origin

Access-Control-Allow-Origin:是后端给前端反馈跨域成功的响应头字段。它的值要么是前端请求时请求头信息里Origin字段的值,要么是一个*,表示接受任意域名的请求。

简单请求和非简单请求

浏览器将Cors请求分成两类:简单请求非简单请求

简单请求

只要同时满足以下两大条件,就属于简单请求。

  • 请求方法是以下三种方法之一:headgetpost
  • HTTP的头信息不超出以下几种字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type(只限于 application/x-www-form-urlencoded、multipart/form-data、text/plain这三种情况)

对于简单请求,浏览器会直接发送Cors请求,具体说来(如果你看懂上面你已经会了😎),就是在Request Headers中加入origin字段,用来说明本次请求来自哪个源,然后后端比对前端发送过来的Origin与后端已经提前设置好的Access-Control-Allow-Origin,决定是否同意这次请求。

😫如果Origin指定的源不在Access-Control-Allow-Origin范围之内,后端就会返回一个正常的HTTP回应告诉你错啦,而且浏览器发现Response Headers信息里不包含Access-Control-Allow-Origin 字段信息。👇

有种特殊情况💣:尽管请求失败,但返回的状态码依然可能为200,表明浏览器请求是发出去了的,后端也会正确返回,但是我们拿不到response的内容。

🤗如果后端发现请求的Origin在指定的源范围内,则会返回响应的请求结果,在Response Headers中会返回后端设置的相关Cors头部字段,包括Access-Control-Allow-Origin字段信息👇

非简单请求

非简单请求,也叫 预检请求。凡是不同时满足上面两个条件,就属于非简单请求。

  • 请求方法是以下方法之一:putdeletepatchconnectoptionstrace
  • Content-Type 的值不属于下列之一:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

当发生符合非简单请求的条件时,浏览器会自动先发送一个options请求,并设置相应的请求头。如果发现后端支持该请求,则会将真正的请求发送到后端,反之,如果浏览器发现后端并不支持该请求,则会在控制台抛出错误,如下:

如果非简单请求发送成功:👇

预检请求

实际请求

蓝框标注的为预检请求发送的查询,在Request Headers内会加入Access-Control-Request-Headers字段和 Access-Control-Request-Method字段。后端支持预检请求后,才会发送真正的请求(如图中红框所示)。可以看到,非简单请求Response Headers除了返回Access-Control-Allow-Origin,还多返回了几个字段:

响应头字段说明
Access-Control-Allow-Headers表明请求时携带的自定义请求首部字段
Access-Control-Allow-Methods表明后端所支持的请求方法
Access-Control-Max-Age表明Cors缓存时间,即本次请求的有效期,单位为秒。在此期间,不会发出另一条预检请求。

写在后面

Jsonp跨域 VS Cors跨域

Jsonp跨域Cors跨域
对浏览器的支持较好大部分浏览器都支持,浏览器IE10以下不支持Cors
只能发送get请求获取资源支持所有类型的HTTP请求(getpostputdelete等)
错误处理机制不完善,如果动态注入js脚本成功,就会执行回调函数,如果错误却没有任何提示信息可以通过onerror事件监听错误,并且浏览器控制台可以看到详细的报错信息,利于排查
需要前端后端共同配合才能跨域成功前端照常发送请求,主要是后端在忙
只会发一次请求Cors的非简单请求会发两次请求(预检请求+实际请求)
callback参数注入和资源访问授权设置存在安全问题可以在资源访问授权方面(Access-Control-Allow-Origin)进行限制
被攻击后也只能读取资源被攻击后不仅能读取资源,还能修改

总结与Cors跨域相关的协议头

请求头(Request Headers)字段说明
Origin当前请求源,发送请求时会自动生成
Access-Control-Request-Method预检请求专属,将实际请求所使用的HTTP方法告诉后端
Access-Control-Request-Headers预检请求专属,将实际请求所携带的首部字段告诉后端
响应头(Response Headers)字段说明
Access-Control-Allow-Origin后端给前端反馈跨域成功的响应头字段。它的值要么是前端请求时请求头信息里Origin字段的值,要么是一个*,表示接受任意域名的请求。
Access-Control-Allow-Headers表明请求时携带的自定义请求首部字段
Access-Control-Allow-Methods表明后端所支持的请求方法
Access-Control-Max-Age表明Cors缓存时间,即本次请求的有效期,单位为秒。在此期间,不会发出另一条预检请求。
Access-Control-Allow-Credentials表明是否允许发送cookie。Cors跨域请求时,浏览器默认不会发送cookie。如果需要跨域传递cookie前端需要设置withCredentials:true,同时后端也必须设置Access-Control-Allow-Credentialstrue
Access-Control-Expose-HeadersCors请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

solo理解,以上如有不对的地方,欢迎指教~

参考资料:

Cors实现请求跨域-机智的赵先生

一文弄懂 CORS 跨域(前端+后端代码实例讲解)-茉莉蜜茶only