一、同源策略和跨域

同源策略是一种约定,它是浏览器最核心也最基本的安全功能。

同源是指"协议+域名+端口"三者都要相同,当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。

二、跨域产生的问题

同源策略会阻止一个域的javascript脚本和另外一个域进行交互,所以通过ajax请求去获取另一个域的内容响应会被浏览器阻止进而失败。注意跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。Cookie、LocalStorage 、SessionStorage在不同域之间也是相互独立的。

但需要注意有三个标签在加载资源时是不受同源策略限制的

img,link,script这三个标签。

三、跨域问题的解决方式

3.1 jsonp

jsonp利用了script标签不受同源策略限制来进行跨域访问,它只支持get请求,并且需要服务端做处理来支持,jquery提供了发起jsonp请求的方式。

3.2 CORS

CORS是一个W3C标准,全称是"跨域资源共享" ,它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS需要浏览器和服务器同时支持。目前,主流浏览器都支持该功能所以仅需要在服务端添加一些CORS相关的请求头。

3.2.1 CORS相关概念

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(预检请求)

简单请求需要同时满足两个条件:

(1) 请求方式只能是HEAD,GET,POST

(2)只能携带这几种请求头:

Accept

Accept-Language

Content-Language

Last-Event-ID

Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain ,

这个条件可以简单记成只要我们想添加一些自定义的请求头时发出的就不是简单请求了。

跨域时浏览器对这两种请求的处理方式是不一样的

3.2.2 简单请求跨域

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中自动增加一个Origin字段来说明本次请求来自哪个源。服务器会根据这个值决定是否允许本次访问。

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应 ,浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出跨域访问的错误。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段

Access-Control-Allow-Origin: http://www.a.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Authorization

(1) Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求

(2) Access-Control-Allow-Credentials

该字段可选,它的值是一个布尔值,表示跨域访问是否允许发送Cookie ,默认是false,需要发送cookie时把该字段指定成true,

这是服务端的设置,同时前端也需要允许携带cookie,如使用ajax时要带上这个属性 withCredentials:true

(3) Access-Control-Expose-Headers

可选字段,CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本响应字段,也不需要记都有哪些,如果我们想获取到自定义的一些响应头就得在这个属性上指定,多个用逗号隔开。

所以前端发出简单请求时后端要做的就是根据Origin字段判断是否允许访问然后设置上边这几个响应头。

3.2.3 非简单请求跨域

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

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

预检"请求用的请求方式是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。 下面是一个预检请求的请求头示例

OPTIONS /cors HTTP/1.1
Origin: http://www.a.com
Access-Control-Request-Method: PUT  像服务器说明请求方式
Access-Control-Request-Headers: X-Custom-Header 像服务器说明会额外发送的请求头,多个以逗号隔开

上边这些操作都是浏览器自动完成的,所以前端不需要特殊处理。

服务器收到"预检"请求以后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。 回应中也会包含Access-Control-Allow-xxx

这种响应头,如果不允许访问就不包含这些请求头

Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers

"预检"请求通过后浏览器就会发出真正的请求进行通讯。上边服务器对请求头的这些处理还是相对比较麻烦的,但如果我们是一个使用了springmvc的项目,springmvc已经提供了一个过滤器,我们只需要做简单的配置就可以实现cors

3.2.4 springmvc中使用CORS

springmvc提供了一个CorsFilter可以用来处理cors,只需要把这个过滤器注册到我们的应用中,这里以springboot工程来举例,在配置类注册如下的bean

    //注册处理跨域的过滤器
    @Bean
    public FilterRegistrationBean filterRegistrationBean(){
        FilterRegistrationBean<CorsFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        //创建跨域配置
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedMethod("GET");
        corsConfiguration.addAllowedMethod("POST");
        corsConfiguration.setAllowCredentials(false);

        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        //注册路径和对应的配置,
        //注意这个方法可以被多次调用,即可以给不同的路径设定不同的跨域配置
        corsConfigurationSource.registerCorsConfiguration("/**",corsConfiguration);
        CorsFilter corsFilter = new CorsFilter(corsConfigurationSource);
        filterRegistrationBean.setOrder(0);
        filterRegistrationBean.setFilter(corsFilter);
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }

3.3 使用代理

跨域问题只在浏览器中用js访问其它域时出现,服务器之间相互调用是不受限制的,所以可以A域对B域的访问可以在中间使用一个代理服务,A--->A的代理--->B ,这样对浏览器来说A和A的代理是同一个域的就不会有跨域问题。

这种方式不需要服务端做适配就能实现跨域访问。

具体的实现方式有vue自带的proxyTable,nginx反向代理,可以参考这篇文章vue+springboot前后端分离工程中跨域问题的解决