跨域问题的解决

Posted by Wh0ami-hy on January 29, 2024

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);  
    }  
}

本站总访问量