1. 简介
主要涉及三个关键词:
- 同源策略(Same-origin policy,简称 SOP)
- 跨站请求伪造(Cross-site request forgery,简称 CSRF)
- 跨域资源共享(Cross-Origin Resource Sharing,简称 CORS)
2. 什么是跨域
跨域请求(Cross-Origin Request)是指在浏览器中发送AJAX请求时,请求的目标资源位于不同的域(域名、端口或协议)下的情况
构成跨域的条件:
- 浏览器发送AJAX请求
- 域名、端口或协议不同
3. 浏览器的同源策略
协议、域名、端口都一样,就是同源
本质上 SOP 并不是禁止跨域请求,而是在请求后拦截了请求的响应
其实 SOP 不是单一的定义,而是在不同情况下有不同的解释:
- 限制 cookies、DOM 和 JavaScript 的命名区域
- 限制 iframe、图片等各种资源的内容操作
- 限制 ajax 请求,准确来说是限制操作 ajax 响应结果
如果没有了 SOP:
- 一个浏览器打开几个 tab,数据就泄露了
- 你用 iframe 打开一个银行网站,你可以肆意读取网站的内容,就能获取用户输入的内容
- 更加肆意地进行 CSRF
4. 跨站请求伪造 CSRF
CSRF(Cross-site request forgery)跨站请求伪造,是一种常见的攻击方式。是指 A 网站正常登陆后,cookie 正常保存,其他网站 B 通过某种方式调用 A 网站接口进行操作,A 的接口在请求时会自动带上 cookie
上面说了,SOP 可以通过 html tag 加载资源,而且 SOP 不阻止接口请求而是拦截请求结果,CSRF 恰恰占了这两个便宜
所以 SOP 不能作为防范 CSRF 的方法
对于 GET 请求,直接放到<img>
就能神不知鬼不觉地请求跨域接口
对于 POST 请求,可以使用 form 提交
对于 ajax 请求,在获得数据之后你能肆意进行 js 操作。这时候虽然同源策略会阻止响应,但依然会发出请求。因为执行响应拦截的是浏览器而不是后端程序。事实上你的请求已经发到服务器并返回了结果,但是迫于安全策略,浏览器不允许你继续进行 js 操作,所以报出你熟悉的 blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource
不过浏览器并不是让所有请求都发送成功,上述情况仅限于简单请求,相关知识会在下面 CORS 一节详细解释
5. 跨域资源共享 CORS
跨域是浏览器限制,但是如果服务器设置了 CORS 相关配置,在返回服务器的信息头部会加上 Access-Control-Allow-Origin
,浏览器看到这个字段的值与当前的源匹配,就会解锁跨域限制
CORS将请求分为:简单请求、非简单请求
5.1. 简单请求
满足以下所有条件,则为简单请求
- 请求方法为GET、HEAD、POST之一
- HTTP头部信息只包含了以下字段:Accept、Accept-Language、Content-Language、Content-Type(但仅限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
处理方法:
对于简单请求,CORS的策略是请求时在请求头中增加一个Origin
字段
Host: localhost:8080
Origin: http://localhost :8081
Referer: http://localhost : 8081/index. html
服务器收到请求后,根据该字段判断是否允许该请求访问,如果允许,则在HTTP头信息中添加Access-Control-Allow-Origin
字段
Access-Control -Allow-Origin: http://localhost:8081
Content-Length: 20
Content-Type: text/plain; charset=UTF-8
Date: Thu, 12 Jul 2018 12:51:14 GMT
5.2. 非简单请求
如果请求不满足简单请求,则为非简单请求
处理方法:
非简单请求会在正式请求之前,先发送一个预检请求,称为OPTIONS请求
OPTIONS请求方法的作用是:用来请求服务器告知其支持的 HTTP 方法和其他一些选项,如是否支持跨域请求、支持哪些请求头等。预检请求会先发送一个OPTIONS请求,服务器响应该请求后,浏览器才会发起正式请求
非简单请求会在请求头中添加一个名为 Origin
的字段,该字段表示源地址,即发起请求的页面或域名。当服务器响应时,如果允许该源地址的请求,则会在响应头中添加一个名为 Access-Control-Allow-Origin
的字段,该字段的值为该源地址,表示允许该源地址的请求
6. CORS 与 cookie
与同域不同,用于跨域的 CORS 请求默认不发送 Cookie 和 HTTP 认证信息,前后端都要在配置中设定请求时带上 cookie。
这就是为什么在进行 CORS 请求时 axios 需要设置 withCredentials: true。
7. 怎么解决跨域问题
跨域问题可以从以下方面解决:
- 应用层面解决:例如 Spring Boot 项目中解决跨域问题。
- 反向代理解决:例如 Nginx 中解决跨域问题。
- 网关中解决:例如 Spring Cloud Gateway 中解决跨域问题。
它们的使用优先级是:网关层 > 代理层 > 应用层。因为越靠前覆盖范围就越大,解决跨域问题就越容易。
7.1. Spring Boot 中解决跨域
在 Spring Boot 中跨域问题有以下 5 种解决方案:
- 使用
@CrossOrigin
注解实现跨域【局域类跨域】 - 通过配置文件实现跨域【全局跨域】
- 通过 CorsFilter 对象实现跨域【全局跨域】
- 通过 Response 对象实现跨域【局域方法跨域】
- 通过实现 ResponseBodyAdvice 实现跨域【全局跨域】
通过注解跨域
@CrossOrigin(origins = "*")
通过配置文件跨域
- 创建一个新配置文件。
- 添加
@Configuration
注解,实现 WebMvcConfigurer 接口。 - 重写 addCorsMappings 方法,设置允许跨域的代码。
@Configuration // 一定不要忽略此注解
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 所有接口
.allowCredentials(true) // 是否发送 Cookie
.allowedOriginPatterns("*") // 支持域
.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"}) // 支持方法
.allowedHeaders("*")
.exposedHeaders("*");
}
}
7.2. Nginx 中解决跨域
在 Nginx 服务器的配置文件中添加以下代码
server {
listen 80;
server_name your_domain.com;
location /api {
# 允许跨域请求的域名,* 表示允许所有域名访问
add_header 'Access-Control-Allow-Origin' '*';
# 允许跨域请求的方法
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
# 允许跨域请求的自定义 Header
add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept';
# 允许跨域请求的 Credential
add_header 'Access-Control-Allow-Credentials' 'true';
# 预检请求的存活时间,即 Options 请求的响应缓存时间
add_header 'Access-Control-Max-Age' 3600;
# 处理预检请求
if ($request_method = 'OPTIONS') {
return 204;
}
}
# 其他配置...
}
上述示例中,location /api
代表配置针对 /api
路径的请求进行跨域设置。可以根据具体需要修改 location 的值和其他相关参数。配置中的 add_header 指令用于设置响应头部,常用的响应头部包括以下这些:
- Access-Control-Allow-Origin:用于指定允许跨域的域名,可以设置为 * 表示允许所有域名访问。
- Access-Control-Allow-Methods:用于指定允许的跨域请求的方法,例如 GET、POST、OPTIONS 等。
- Access-Control-Allow-Headers:用于指定允许的跨域请求的自定义 Header。
- Access-Control-Allow-Credentials:用于指定是否允许跨域请求发送和接收 Cookie。
- Access-Control-Max-Age:用于设置预检请求(OPTIONS 请求)的响应缓存时间。
7.3. 网关中解决跨域
Spring Cloud Gateway 中解决跨域问题可以通过以下两种方式实现:
- 通过在配置文件中配置跨域实现。
- 通过在框架中添加 CorsWebFilter 来解决跨域问题。
配置文件中设置跨域
在 application.yml 中添加以下配置
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]': # 这里的'/**'表示对所有路由生效,可以根据需要调整为特定路径
allowedOrigins: "*" # 允许所有的源地址,也可以指定具体的域名
allowedMethods: # 允许的 HTTP 方法类型
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowedHeaders: "*" # 允许所有的请求头,也可以指定具体的请求头
allowCredentials: true # 是否允许携带凭证(cookies)
maxAge: 3600 # CORS预检请求的有效期(秒)
添加 CorsWebFilter 来解决跨域问题
Spring-Framework 5.3 版本之后,关于 CORS 跨域配置类 CorsConfiguration 中将 addAllowedOrigin 方法名修改为 addAllowedOriginPattern,因此配置了变成了以下这样
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
// 这里仅为了说明问题,配置为放行所有域名,生产环境请对此进行修改
config.addAllowedOriginPattern("*");
// 放行的请求头
config.addAllowedHeader("*");
// 放行的请求类型,有 GET, POST, PUT, DELETE, OPTIONS
config.addAllowedMethod("*");
// 暴露头部信息
config.addExposedHeader("*");
// 是否允许发送 Cookie
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}