为什么会出现跨域的现象?
因为在现在大多数都是前后端分离开发的情况下,前端资源和接口域名不同,触发了浏览器的同源安全策略,当接口返回数据到浏览器端时,被浏览器拦截,也就是我们平时所说的跨域。 所以整个跨域产生的根源是浏览器
同源指的是,协议、域名、端口都相同的Url,同源策略属于浏览器的核心安全功能,能够很好的防御XSS、CSRF攻击。
什么是CORS?
CORS,全称是"跨域资源共享",它允许浏览器向跨域服务器发送XMLHttpRequest请求,从而克服了原先只能请求同域服务器的限制。整个跨域过程都是浏览器在参与,对于前端开发者来说,CORS请求和普通请求在代码上没有区别。
目前几乎所有浏览器都支持CORS。IE9以及以下版本不支持,需要采用其它跨域方案。
IE9以及以下版本跨域解决方案
-
JSONP(JSON with Padding):
- JSONP 是一种通过动态创建<script>元素进行跨域请求的方法。服务器返回的数据被包裹在回调函数中,该回调函数会在客户端执行。这种方法仅支持 GET 请求,且需要服务器支持返回 JSONP 格式的数据。
-
代理服务器:
- 在同源策略下,通过在同一域中设置一个代理服务器,将跨域请求发送到代理服务器,再由代理服务器转发请求到目标服务器,并将响应返回给客户端。这样可以绕过浏览器的同源策略。
-
XDomainRequest:
- Internet Explorer 8 和 9 提供了 XDomainRequest 对象,可以用于发送跨域请求。但是需要注意,它仅支持一部分的 HTTP 功能,并且有一些限制。
CORS涉及到的配置项
- Access-Control-Allow-Origin
- Access-Control-Allow-Credentials
- Access-Control-Request-Method
- Access-Control-Allow-Methods
- Access-Control-Request-Headers
- Access-Control-Allow-Headers
- Access-Control-Expose-Headers
- Access-Control-Max-Age
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
- ....还有协议其它冷门header浏览器默认携带的
简单请求响应过程流程
1、浏览器发出CORS请求️并在头信息添加一个Origin字段,该Origin字段用来标识本次请求来自哪个源(协议、域名、端口)。
例如:目前我们的页面域名大多数是h5-cdn.58.com,接口是其它域名,所以我们的请求头都有一个Origin字段

2、服务器收到请求后,执行业务逻辑。根据请求头Origin判断是否允许跨域
- 如果Origin在白名单内,那么服务器返回的响应将会多出几个信息头

Access-Control-Allow-Credentials: true: 用于在请求要求包含 credentials(Request.credentials 的值为 include)时,告知浏览器是否可以将对请求的响应暴露给前端 JavaScript 代码。
如果是CORS请求,前端加了Request.credentials,那么服务端必须添加Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://h5-cdn.58.com:CORS响应头中必须存在的字段。它的值要么是请求携带的Origin值,要么是*:表明接受任意域名请求
- 如果Origin不在白名单内,那么服务端将返回正常的相应。

3、浏览器在收到服务端响应后,会判断Access-Control-Allow-Origin等是否符合条件,如果不符合,将会拦截响应,触发跨域
复杂请求
如果请求域名和页面域名相同,那么就不会存在复杂请求这一说法,简单请求就可以满足需求。
触发条件
- 使用了PUT、DELETE等请求方法。
- 使用了自定义请求头
- Content-Type 为 application/json ⭐



Access-Control-Request-Headers【Request Headers】
用在预检请求中,浏览器使用该头部通知服务器真正的请求将要使用的自定义请求标头字段。
Access-Control-Allow-Headers【Response Headers】
用于预检请求中,列出了将会在正式请求的 Access-Control-Request-Headers 字段中出现的首部信息。
- 不设置credentials
- CORS请求,不携带任何Cookie。即便Cookie是同站点。
- 非CORS请求(同域)场景下,会默认携带同站点的Cookie。
- 设置credentials
- CORS请求场景,携带同站点的Cookie。
- 非CORS请求(同域)场景下,携带同站点的Cookie。所以该场景可以不设置credentials
同站:域名相同或者域名之间为子域名的关系
Access-Control-Request- Method 【Request Headers】
用于通知服务器在真正的请求中会采用哪种HTTP方法。因为预检请求所使用的方法总是OPTIONS,与实际请求所使用的方法不一样,所以这个请求头是必要的。
Access-Control-Allow-Methods 【Response Headers】
对预检请求的应答中明确了客户端所要访问的资源允许使用的方法或方法列表。
那么针对于简单请求或者同域请求时,服务端未设置Access-Control-Allow-Methods,我们能正常请求吗?
可以的。
简单请求:浏览器会默认允许的方法是 GET、POST 和 HEAD。同域请求:默认所有的请求方法都支持。
Access-Control-Expose-Headers 【Response Headers】
允许服务器指示那些响应标头可以暴露给浏览器中运行的脚本,以响应跨源请求。CORS默认情况下,JS只能访问到以下其中Response Headers:

如果是非CORS请求,那么默认是可以访问到所有的响应头的。
Access-Control-Max-Age 【Response Headers】
表示预检请求的返回结果(即 Access-Control-Allow-Methods 和Access-Control-Allow-Headers 提供的信息)可以被缓存多久。单位:秒。
为什么要发送预检请求?
预检请求出发的前提
- 跨域请求
- 复杂请求类型,使用了自定义header或者常规请求以外的类型。
基于以上两点前提,预检请求的存在
- 能给确保浏览器只在服务器确认安全的情况下发送实际请求。这有助于防止恶意网站发起对用户敏感数据的恶意请求,可以避免跨域请求对服务器用户数据产生未预期的影响。
- 不被允许的请求提前被拦截,能给减少一些无用的运算逻辑。
域名添加了页面域名为白名单,但是前端进行post请求时,还是会出现跨域,why?
- 如果是简单请求,添加白名单就可以解决跨域问题。
- 如果是复杂请求,需要后端同学在响应头内添加Access-Control-Allow-Headers、Access-Control-Allow-Methods字段
-
- Access-Control-Allow-Methods的值:设置成真实请求的请求方法,值和Access-Control-Request-Method对应。
- Access-Control-Allow-Headers的值:添加的自定义请求header,值和Access-Control-Request-Headers对应。
Post请求是不会导致请求变为复杂请求,但是我们平时使用的请求库Axios或者其它,当不传Content-Type时,会自动根据数据类型设置Content-Type。
如果数据是普通对象,请求的Content-Type会默认设置为application/json。 如果是URLSearchParams对象,那么会默认设置为application/x-www-form-urlencoded;charset=utf-8。
这也是平时我们没有设置Content-Type和自定义header,请求却变成了复杂请求的大多数原因。