最近在业务代码中深受跨域问题困扰,所以特别写一篇博客来记录一下本身对跨域的理解以及使用到的参考资料。本文的项目背景基于vue+vuex+axios+springboot。涉及如下内容:php
若是对跨域有所了解的盆友能够直接跳到SpringBoot配置部分查看具体配置,或者是参考文章末尾Spring官网对CORS配置的博客连接。html
跨域是指当一个资源从与该资源自己所在的服务器不一样的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。这里盗用MDN上的一张图:前端
当一个域名向另外一个不一样的域名发起请求时,这时就产生了跨域问题。
那么为何会出现跨域这样的概念呢?这就要提到以前规定的same origin policy
。如下是引自维基百科的对于同源政策的描述:vue
An origin is defined by the scheme, host, and port of a URL. Generally speaking, documents retrieved from distinct origins are isolated from each other. For example, if a document retrieved from http://example.com/doc.html tries to access the DOM of a document retrieved from https://example.com/target.html, the user agent will disallow access because the origin of the first document, (http, example.com, 80), does not match the origin of the second document (https, example.com, 443).
总而言之,同源就是指拥有一样的schema,主机和端口号的URL,不知足以上三点的任何一点都表明着这两个URL非同源,它们之间的相互访问就会产生跨域问题。ios
这里再借用MDN上的URL是否同源的例子:web
而在HTTP访问中,又有了些许的变化。好比咱们一般会从CDN上获取CSS,JS等静态资源,而这些静态资源的域名和当前的域并不一样源,可是HTTP容许这样的跨域访问。所以,咱们能够将HTTP上的跨域分为三类:面试
<link rel="stylesheet" href="...">
得到CSS文件,<img>
标签引入另外一个源的图片这里简单介绍一下有名的CSFR攻击来讲明同源政策的目的。
这里引用维基百科对跨站请求攻击的解释:spring
跨站请求攻击,简单地说,是攻击者经过一些技术手段欺骗用户的浏览器去访问一个本身曾经认证过的网站并执行一些操做(如发邮件,发消息,甚至财产操做如转帐和购买商品)。因为浏览器曾经认证过,因此被访问的网站会认为是真正的用户操做而去执行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求自己是用户自愿发出的。
引用网上的一张图片:vuex
简单的解释一下跨站请求的实现,维基百科上也有很是详细的例子。
假设如今用户在网页上进行转帐,只有经过身份验证的用户才能够进行该操做。假设服务器是经过这样的一个URL http://bank.example/withdraw?account=a&amount=1000000&for=b
实现a向b转帐100000块的业务。由于该请求会携带用户的身份认证信息,所以它可以经过服务器的认证并实现操做。可是这时恶意用户c但愿用这样一个形式的URLhttp://bank.example/withdraw?account=a&amount=1000000&for=c
由于c本身并不具备a的session,所以他会经过别的方式诱惑a用户执行这个操做。好比它会经过发送恶意邮件的方式骗a点击上面的超连接。a若是此时并无退出bank.example
的登陆即其session信息未被清空,那么a将成功经过服务器的认证进行转帐,实现了c的“心愿”。其它的还有诸如在用户进入恶意网站后利用js脚本自动提交表单向bank.example
发出带有a的session的post请求等等。json
同源政策将会确保网站a拒绝来自网站b的请求。
当前端框架兴起以后,先后端完全分离的开发方式渐渐流行。前端和后端每每部署在不一样的域名之上。前端经过访问后端的API获取数据,渲染前端界面,甚至进行路由跳转。这一般意味着先后端会出现不一样源的问题。由于即便部署在同一台主机上,两者也属于不一样的端口。那么咱们就须要某种策略使得跨域请求可以经过。支持跨域的方式有不少,下文主要介绍后端Spring Boot配置支持跨域访问。
以前配置Spring Boot跨域的时候,我都是直接从网上抄一段这样的代码:
@Configuration public class WebConfig extends WebMvcConfigurationSupport { ...... public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/***") .allowedHeaders("**") .allowedMethods("GET", "POST") .allowedOrigins("*"); } }
一开始在开发过程当中,这段代码并无问题。可是在试图接入登陆业务以后出现了问题。由于采用Dubbo进行微服务开始,咱们决定将登陆做为一个单独的业务独立部署在另外一个容器中。登陆业务的基本流程是访问登陆容器,登陆成功后返回一个token存储在服务器的localStorage中。以后每次访问别的服务时都会在header中携带这个token,服务利用拦截器对token进行解析,判断其是否合法以及是否生效,若是合法则将解析结果放入request中传递给后面的Controller。
在上面这个配置的基础上出现了几个问题:
access-control-allow-origin
字段!也就是说响应被拦截器拦截,甚至没有进入跨域访问的响应逻辑。而我使用axios时由于这个响应报文最后被认为是跨域问题,没法从error中得到401的状态码。总之,由于不知道一个真正的跨域请求的报文应该是什么样子的,因此盲目的折腾了半天,甚至没能将问题定位到后端的跨域配置。因此,如今来看一下真正的跨域请求报文到底是什么样子的,来了解一下跨域的原理。
在次以前,先了解一下preflight。
咱们去查看浏览器发出的跨域请求时,常常会看到一个OPTION报文,它的url和真正的GET或是POST请求的URL相同。这个OPTION请求就是传说中的preflight请求。preflight请求是为了询问服务器该跨域请求是否能够被识别或是被容许。
preflight报文一般长成这样:
OPTIONS /resource/foo Access-Control-Request-Method: DELETE Access-Control-Request-Headers: origin, x-requested-with Origin: https://foo.bar.org
若是容许来自该IP的跨域访问,服务器会用Access-Control-Allow-Origin
头字段说明允,并在Access-Control-Allow-Methods
指明容许的方法。preflight响应报文一般长成这样:
HTTP/1.1 200 OK Content-Length: 0 Connection: keep-alive Access-Control-Allow-Origin: https://foo.bar.org Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE Access-Control-Max-Age: 86400
这里Access-Control—Max-Age
指定了在86400s内无需为该URL发送preflight请求。
至于为什么须要preflight请参考reference中的文章。
并非全部的请求都须要发送preflight请求,服务器面对简单请求会直接返回Access-Control-Allow-Origin
响应头来讲明它的跨域访问是否经过,若是经过,则会在响应体中直接携带数据。请求和响应报文以下:
GET /resources/public-data/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Referer: http://foo.example/examples/access-control/simpleXSInvocation.html Origin: http://foo.example HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 00:23:53 GMT Server: Apache/2.0.61 Access-Control-Allow-Origin: * Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: application/xml [xml]
服务器会检查origin字段的URL是否容许跨域请求。能够看到该服务器容许来自一切IP的跨域访问,由于它返回的响应头为Access-Control-Allow-Origin: *
。
你会发现,这里的请求和通常的HTTP请求并无太大的差异。
那么,什么是简单请求呢?
知足以上要求的则为简单请求。而一般先后端分离的服务之间会经过json形式的数据进行沟通,即content-type为application/json。而这种形式不符合简单请求的定义,所以须要使用option请求进行预检。
复杂请求的预检请求报文以下:
OPTIONS /resources/post-here/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Origin: http://foo.example Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER, Content-Type
请求头中有两个字段比较特殊。Access-Control-Request-Method
说明真正的跨域请求的方法,这里是POST方法,而Access-Control-Request-Headers
则说明请求头中包含哪些非简单字段。服务器端会根据自身的配置查看是否支持包含这些非简单字段的请求,若是不包含,则该跨域请求会被拒绝。
预检响应报文以下:
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400 Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
这里Access-Control-Allow-Headers
说明了服务器支持的跨域请求的这些字段。
以后服务器会发送真实的请求,服务器会对之响应,其响应头中会包含Access-Control-Allow-Origin
字段。
如今咱们再来看一下以前的配置:
@Configuration public class WebConfig extends WebMvcConfigurationSupport { ...... public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/***")//对/api/**进行跨域配置 .allowedHeaders("**")//容许全部的非简单请求头 .allowedMethods("GET", "OPTIONS", "POST") //容许三种方法 .allowedOrigins("*");//容许来自全部域的请求 } }
固然这种所有符合的通配符并非一个很好的选择,咱们应当限制跨域请求的形式,从而拒毫不符合要求的请求。
第二种配置是采用Filter的形式进行配置。位于最前面的Filter会在请求进入任何其它位置以前对其进行处理。
@Configuration public class MyConfiguration { @Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("http://domain1.com"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean.setOrder(0); return bean; } }
这里跟上面的配置意思基本相同,区别在于这里引入了setAllowCredential配置。它表明服务器支持跨域时携带认证信息。须要注意的是,若是开启这个配置,则allowedOrigins不能够为*。
springboot设置cors跨域请求的两种方式
spring官网-设置容许跨域请求
MDN Http 控制访问
MDN Same Origin Policy
What's the motivation of preflight
若想了解更多技术资讯、面试教程以及互联网公司的内推信息,欢迎关注个人公众号!