type
status
date
slug
summary
tags
category
icon
password
一、跨域介绍
跨域是什么
跨域(SOP)问题指的是不同站点之间,使用 ajax 无法相互调用的问题。跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。在请求时,如果出现了以下情况中的任意一种,那么它就是跨域请求:
- 协议不同,如 http 和 https。
- 域名不同。
- 端口不同。
比如我们从
https://www.baidu.com
发起一个请求本地地址 http://localhost:8001/test/query
的请求,具体怎么发起参考下面的模拟跨域。会返回下面的错误提示常见的出现跨域的情形如下图所示:
注意跨域限制是浏览器行为,不是服务器行为。这也是为什么跨域通过 postman 这类工具来直接请求服务器接口数据,而在网页中通过js就不可以的原因,因为 js 运行在浏览器,当 js 运行的环境 url 和请求的接口数据的url不同域,并且服务器没有允许跨域请求时,浏览器就会认为这个请求是不安全的,就会限制请求。
模拟跨域
我们可以在 Chrome 的控制里面模拟跨域,这样我们就不需要特地写一个前端项目来测试跨域问题。
先随便打开一个网页,例如百度首页
https://www.baidu.com
,F12打开控制台,输入下面脚本。GET请求
POST请求
二、CORS介绍
CORS是什么
CORS(Cross-origin resource sharing)就是为了解决 SOP 问题而生的。CORS 是一个 W3C 标准,用于允许浏览器向跨源(协议 + 域名 + 端口)服务器发起 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。 CORS 需要浏览器和服务器同时支持,基于 CORS 机制发出跨源请求时,请求-响应过程如下:
- 浏览器在请求中添加来源标头,其中包含有关当前来源的协议、主机和端口的信息
- 服务器检查当前来源标头,然后使用请求的数据和 Access-Control-Allow-Origin 标头进行响应
- 浏览器查看访问控制请求标头并与客户端应用程序共享返回的数据
CORS 的通信过程,都是浏览器自动完成,不需要用户参与。对于用户来说,CORS 通信与同源的 AJAX/Fetch 通信没有差别,代码完全一样。浏览器一旦发现请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。 因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
简单请求和非简单请求
浏览器将 CORS 请求分成两类:简单请求(simple request) 和 非简单请求(not-so-simple request)。
- 简单请求:HEAD、GET、POST请求,并且HTTP的头信息不超出以下几种字段 Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type 注:Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
- 除了简单请求之外的其他请求。
两者的使用区别是:
- 简单请求:浏览器发出CORS简单请求,只需要在头信息之中增加一个 Origin 字段。
- 非简单请求:浏览器发出CORS非简单请求,会在正式通信之前,增加一次OPTIONS查询请求,称为"预检"请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
跨域主要涉及下面响应头:
Access-Control-Allow-Origin
该字段必填。它的值要么是请求时 Origin 字段的具体值,要么是一个*
表示接受任意域名的请求。(预检请求和正式请求在跨域时候都会验证)。出于安全起见,在生产环境不建议设置为*
,只添加几个信任的域名。
Access-Control-Allow-Methods
该字段必填。它的值是逗号分隔的一个具体的字符串或者*
,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。(只在预检请求验证)
Access-Control-Allow-Headers
该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。(只在预检请求验证)
Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许跨域使用cookies,如果要跨域使用cookies,可以添加上此请求响应头,值设为true(设置或者不设置,都不会影响请求发送,只会影响在跨域时候是否要携带cookies,但是如果设置,预检请求和正式请求都需要设置)。不过不建议跨域使用(项目中用到过,不过不稳定,有些浏览器带不过去),除非必要,因为有很多方案可以代替。
Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。在有效期间,不用发出另一条预检请求。在开发中,发现每次发起请求都是两条,一次 OPTIONS,一次正常请求,注意是每次,那么就需要配置 Access-Control-Max-Age,避免每次都发出预检请求。
Spring 应用解决跨域问题
解决跨域问题的本质都是给响应头中加了一个 Access-Control-Allow-Origin 的响应头而已。有下面五种解决方案。
1、继承 WebMvcConfigurer
继承
WebMvcConfigurer
重写 addCorsMappings
,对全局有效。这种方法最简单,推荐使用。旧版本的 Spring 的写法是继承WebMvcConfigurerAdapter
来重写addCorsMappings
。Spring 5.x 以后已经抛弃了WebMvcConfigurerAdapter
,官方给出的解释是 WebMvcConfigurer has default methods (made possible by a Java 8 baseline) and can be implemented directly without the need for this adapter,大概意思是说WebMvcConfigurer
接口的方法都改成 default 了,所以不需要WebMvcConfigurerAdapter
(后者其实就是前者的抽象实现类,方法都是空实现,来避免实现接口的时候需要实现所有的方法)。所以尽量不要再使用WebMvcConfigurerAdapter
。
这种方式有一个问题,如果在继承类中同时重写了
addInterceptors
和 addCorsMappings
方法,例如:这种情况下
addInterceptors
会覆盖 addCorsMappings
,处理跨域问题的代码没有执行。2、通过 CorsFilter
定义一个配置类,通过
@Bean
注入 CorsFilt
类型的 Bean。对全局有效。对全局有效。3、实现 Filter 接口
实现 Filter 接口,并把当前类通过
@Component
注入到容器。对全局有效。4、使用 CrossOrigin 注解
这种方式只对局部有效。
- 在conttoller上使用
@CrossOrigin
- 在方法上使用注解
@CrossOrigin
5、Response 手动设置响应头
此方式是解决跨域问题最原始的方式,但它可以支持任意的 Spring Boot 版本(早期的 Spring Boot 版本也是支持的)。但此方式也是局部跨域,它应用的范围最小,设置的是方法级别的跨域。
参考
- Author:mcbilla
- URL:http://mcbilla.com/article/ac8f37c6-527d-46a9-81b5-1a4c30c83c58
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts