type
status
date
slug
summary
tags
category
icon
password

一、跨域介绍

跨域是什么

跨域(SOP)问题指的是不同站点之间,使用 ajax 无法相互调用的问题。跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。在请求时,如果出现了以下情况中的任意一种,那么它就是跨域请求:
  1. 协议不同,如 http 和 https。
  1. 域名不同。
  1. 端口不同。
比如我们从 https://www.baidu.com 发起一个请求本地地址 http://localhost:8001/test/query 的请求,具体怎么发起参考下面的模拟跨域。会返回下面的错误提示
常见的出现跨域的情形如下图所示:
notion image
注意跨域限制是浏览器行为,不是服务器行为。这也是为什么跨域通过 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 机制发出跨源请求时,请求-响应过程如下:
  1. 浏览器在请求中添加来源标头,其中包含有关当前来源的协议、主机和端口的信息
  1. 服务器检查当前来源标头,然后使用请求的数据和 Access-Control-Allow-Origin 标头进行响应
  1. 浏览器查看访问控制请求标头并与客户端应用程序共享返回的数据
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
这种方式有一个问题,如果在继承类中同时重写了 addInterceptorsaddCorsMappings 方法,例如:
这种情况下 addInterceptors 会覆盖 addCorsMappings,处理跨域问题的代码没有执行。

2、通过 CorsFilter

定义一个配置类,通过 @Bean 注入 CorsFilt 类型的 Bean。对全局有效。对全局有效。

3、实现 Filter 接口

实现 Filter 接口,并把当前类通过 @Component 注入到容器。对全局有效。

4、使用 CrossOrigin 注解

这种方式只对局部有效。
  1. 在conttoller上使用 @CrossOrigin
  1. 在方法上使用注解 @CrossOrigin

5、Response 手动设置响应头

此方式是解决跨域问题最原始的方式,但它可以支持任意的 Spring Boot 版本(早期的 Spring Boot 版本也是支持的)。但此方式也是局部跨域,它应用的范围最小,设置的是方法级别的跨域。

参考

Spring MVC:Interceptor拦截器Spring系列:AOP使用
mcbilla
mcbilla
一个普通的干饭人🍚
Announcement
type
status
date
slug
summary
tags
category
icon
password
🎉欢迎来到飙戈的博客🎉
-- 感谢您的支持 ---
👏欢迎学习交流👏