问题描述
浏览器发送 HTTP 请求,当协议、域名、端口号有任一个与当前不同时,即不满足同源策略,就会存在跨域问题,<img>、<script>、表单提交操作不受同源策略影响。
同源策略是为了防止本地敏感数据例如 Cookie、Storage 等被恶意脚本访问、修改。例如前端部署在 a.com,代码中引用了 b.com 的 b.js 文件,这个 b.js 文件就无法直接读取 a.com 下的 Cookie 等数据,但也有例外,如 a.com 中暴露了全局变量、b.js 也可以操作页面 DOM。
解决跨域的常用手段有 JSONP,代理方式,CORS。
JSONP
JSONP 利用了 <script> 标签不受同源策略限制的特点,只支持 get 请求且不够灵活,不建议使用。
代理方式
代理方式不是最终手段,只适用于开发阶段或前端资源和代理服务器同源的情况。浏览器请求同源的代理服务器,代理服务器不受同源策略影响,可以正常与后端服务器通信。
CORS
跨源资源共享(CORS,Cross-Origin Resource Sharing),是解决跨域的最根本手段。
浏览器将发送的请求分为两类:简单请求 和非简单请求。
简单请求需要满足以下条件:
- 请求方法:只能是
GET、HEAD、POST之一; - 请求头:只包含简单头部,即
Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type; - Content-Type:只能是
application/x-www-form-urlencoded、multipart/form-data、text/plain。
不满足上述的就是非简单请求,下面是两种请求的不同处理方法:
对于简单请求,且是跨域请求,则自动在请求头添加一个 Origin 字段,来说明本次请求来自的源,服务器根据这个字段与事先的规则配置来判断是否允许这个源。如果允许,则会在响应头上设置 Access-Control-Allow-Origin 等字段,浏览器拿到响应信息后根据这些字段判断根据同源策略是否要滤掉该响应信息。如果服务端不认可这个源,它也会返回正常的响应,只不过没有额外的响应头,浏览器判断后会过滤掉这个响应。
即就是,跨域请求可以被服务器接收并响应,只不过被浏览器过滤了,只有后端配置了 CORS,设置了响应头,浏览器拿到响应进行判断后,予以放行。
对于非简单请求,在第一次跨域请求之前,浏览器会发送一个预检请求(Option 类型),包含了上面提到的 Origin 还有请求方式等信息,作用就是和服务器沟通是否认可这个源,如果服务器认可,则做出响应,浏览器会缓存这个响应的信息,在有效期内,以后发送跨域请求时都不会再额外发送预检请求。
下面是响应头的一些字段说明:
- Access-Control-Allow-Origin:必须,认可的源,要么等于请求头的 Origin,要么等于 *,表示通通认可。
- Access-Control-Request-Method:必须,认可的请求方法。
- Access-Control-Allow-Credentials:可选,表示服务器端是否想要接收 Cookie,需要注意的是,即便设置为 true,浏览器也不一定发送 Cookie,还必须要满足域名匹配,并且再前端发送请求时配置 withCredentials 属性,并且 Access-Control-Allow-Origin 不能配置为 *。
- Access-Control-Max-Age:可选,指定本次预检请求的缓存有效期。
SpringBoot CORS 配置
- 配置类方式
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 注册一个 CORS 映射
registry.addMapping("/**") // 所有的请求路径
.allowedOrigins("*") // 允许所有源
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的方法
.allowedHeaders("*") // 允许所有的请求头
.allowCredentials(true) // 是否允许携带凭证(Cookie等)
.maxAge(3600); // 预检请求的有效期
}
}- 配置文件方式
spring:
mvc:
cors:
mappings:
"/**":
allowed-origins: "*"
allowed-methods: "GET,POST,PUT,DELETE,OPTIONS"
allowed-headers: "*"
allow-credentials: true
max-age: 3600Nginx CORS 配置
注意,不仅 options 请求要设置 cors 响应头,实际请求也需要设置。
- 静态资源跨域配置(添加到 location 块中)
# 添加跨域支持
add_header 'Access-Control-Allow-Origin' '*';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET,POST,PUT,DELETE,PATCH,OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Range,Range';
add_header 'Access-Control-Max-Age' 1728000;
return 204;
}- 后端接口代理跨域配置(添加到 location 块中)
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Max-Age' 3600;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Allow-Credentials' 'true';
proxy_pass http://backend_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}