跨域问题的解决

一、什么是跨域?

当我们在前端页面点击某个请求时,有时候会在浏览器控制台看到如下红色提示,若出现相关提示,则代表你的请求存在跨域问题了.

 

那么,跨域就是指跨域名的访问,以下三种情况都属于跨域:

1.协议不同

2.域名不同

3.端口号不同

相对于http://www.langshaonian.com/article(http请求默认端口80),通过举例加以说明:

https://www.langshaonian.com/article 协议不同
http://www.langshaonian.cn/article 域名不同
http://www.langshaonian.com:8081/article 端口不同
http://www.langshaonian.com/photo 协议,域名,端口都相同,请求路径不同,不属于跨域

 

 

 

 

 

 

二、跨域问题是怎样产生的?

由于浏览器的同源策略,所以才会存在跨域问题.那么我们必须先弄懂什么是浏览器的同源策略.

2.1)浏览器同源策略概念

同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。--摘取自百度百科

2.2)同源策略分类

  1. DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
  2. XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。

2.3)同源策略存在的意义

因为跨域问题是浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是当前页同域名的路径,这能有效的阻止跨站攻击。

假设场景一:

不法分子做了一个主网站,使用iframe嵌套了一个银行网站,这时用户通过输入账户和密码进行登录,如果不存在DOM同源策略,主网站可以通过DOM节点跨域访问,这样就能拿到用户的账户和密码了.

假设场景二:

用户登录了银行系统,银行系统会向客户端cookie中添加唯一的用户标识,此时用户浏览了恶意网页,并执行了恶意网页中的ajax请求,如果没有XMLHttpRequest 同源策略,请求会携带cookie再次访问银行系统,银行系统从cookie中取出用户的唯一标识,验证正确,数据就会通过响应体发送给恶意网页,导致数据泄露了.

三、跨域带来的问题

我们都知道,在实际的生产环境中,肯定存在多台服务器之间的相互访问,难免地址和端口有所不同,那么如何解决跨域问题,就变得至关重要了.

四、跨域的解决方案

目前比较常用的跨域解决方案有三种(这里小编主要讲解第三种)

1.Jsonp

最早的解决方案,其原理是利用script标签可以跨域的原理实现。缺点是只能发起GET请求

2.nginx反向代理

浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端,并且支持各种请求方式.缺点是需要在nginx进行额外的配置,语义不清晰.

3.CORS(Cross-origin resource sharing)跨域资源共享

CORS是一个W3C标准,是一种规范化的跨域请求解决方案,安全可靠,并且支持各种请求方式.它允许浏览器向跨源服务器发送XMLHttpRequest请求,从而克服了Ajax只能同源使用的限制.

CORS需要浏览器和服务器的同时支持(目前除IE浏览器10版本以下,其他浏览器都支持).在整个CORS通信过程中,都是浏览器自动完成的,不需要用户参与。CORS通信与AJAX没有任何差别,因此不需要改变以前的业务逻辑。浏览器一旦发现 AJAX 请求跨源,就会在请求中携带一些头信息,有时还会多出一次附加的请求,称为预检请求,但用户不会有感觉。

首先浏览器会将ajax请求分为两类,并且对于这两类请求的处理方式还有所不同:

3.1)简单请求

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

1.请求方法时以下三种之一:

  • HEAD
  • GET
  • POST

2.HTTP的头信息不超过以下几种字段:

Accept

Accept-Language

Content-Language

Last-Event-ID

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

当浏览器发现是简单请求时,会在请求头上携带一个Origin字段.该字段展示的就是当前的请求所在的域(协议+域名+端口).

然后服务器会根据其值判断是否在允许跨域的列表之内,如果允许其跨域访问,服务器需要在响应头中携带以下信息

Access-Control-Allow-Origin: http://manage.leyou.com     <!--可允许跨域的域,是一个具体域名或者*,代表任意-->
Access-Control-Allow-Credentials: true                   <!--是否允许携带cookie,默认情况下,cors不会携带cookie,除非这个值是true-->

注意:如果跨域请求要操作cookie,需要满足以下三个条件:

  • 响应头中的Access-Control-Allow-Origin一定不能为* ,必须指定具体的域名

  • 服务的响应头中需要携带Access-Control-Allow-Credentials并且为true。

  • 浏览器发起的ajax请求需要指定withCredentials 为true

3.2)特殊请求

只要不是简单请求,就会被浏览器判定为特殊请求.

特殊请求会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight).浏览器先询问服务器,当前页面所在的域名是否在服务器的许可名单中,以及可以使用哪些请求方法和头信息字段,只有得到肯定答复,浏览器才会发出正式的XMLHttprequest请求.

一个预检请求模板:

OPTIONS /cors HTTP/1.1
Origin: http://manage.leyou.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.leyou.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

与简单请求相比,除了Origin,这里还多了两个头信息:

  • Access-Control-Request-Method:用到的请求方法
  • Access-Conrol-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://manage.leyou.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 1728000
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

与简单请求相比,这里有多出三个额外的头信息:

  • Access-Control-Allow-Methods:允许访问的方法

  • Access-Control-Allow-Headers:允许携带的头

  • Access-Control-Max-Age:本次许可的有效时长,单位是秒,过期之前的ajax请求就无需再次进行预检了

如果浏览器得到上述响应,则认定为可以跨域,后续就跟简单请求的处理是一样的了。

3.3)CORS的具体实现(上代码)

服务器端可通过拦截器实现,并且SpringMVC已经帮我们写好了CORS的跨域过滤器:CorsFilter ,其内部已经实现了上述所讲的判定逻辑。

可以将GlobalCORSConfig配置类放在应用的入口(比如对于SpringCloud可将其放在网关微服务中)

package com.leyou.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class GlobalCORSConfig {
    @Bean
    public CorsFilter corsFilter() {
        //1.添加被允许访问的域,主要不要写*,否则cookie无法使用
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("http://manage.leyou.com");
        //2.设置是否发送cookie信息
        corsConfiguration.setAllowCredentials(true);
        //3.添加允许的请求方式
        corsConfiguration.addAllowedMethod("OPTIONS");
        corsConfiguration.addAllowedMethod("HEAD");
        corsConfiguration.addAllowedMethod("GET");
        corsConfiguration.addAllowedMethod("POST");
        corsConfiguration.addAllowedMethod("PUT");
        corsConfiguration.addAllowedMethod("DELETE");
        //4.添加允许的头信息
        corsConfiguration.addAllowedHeader("*");
        //5.设置预检请求有效期,单位:秒
        corsConfiguration.setMaxAge(3600*24L);
        //6.添加映射路径,拦截一切请求
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/**",corsConfiguration);
        //7.返回新的CORSFilter
        return new CorsFilter(corsConfigurationSource);
    }
}

 

posted @ 2020-03-11 17:09  狼_少_年  阅读(564)  评论(0编辑  收藏  举报