跨域问题的解决
一、什么是跨域?
当我们在前端页面点击某个请求时,有时候会在浏览器控制台看到如下红色提示,若出现相关提示,则代表你的请求存在跨域问题了.
那么,跨域就是指跨域名的访问,以下三种情况都属于跨域:
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)同源策略分类
- DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
- XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。
2.3)同源策略存在的意义
假设场景一:
不法分子做了一个主网站,使用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-Headers:允许携带的头
-
Access-Control-Max-Age:本次许可的有效时长,单位是秒,
如果浏览器得到上述响应,则认定为可以跨域,后续就跟简单请求的处理是一样的了。
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); } }