【问题排查】Spring Cloud Gateway 返回 403 状态码

现象

  • GET 请求正常返回
  • POST 请求返回 403 Forbidden

gateway access 日志:

2022-01-11 11:56:52,488 : 10.0.16.132:15362 - - [11/Jan/2022:11:56:52 +0800] "GET /landing-page/experience-train-enroll/status?phoneNumber=15074470123 HTTP/1.1" 200 85 44 ms
2022-01-11 11:56:52,589 : 10.0.16.132:15362 - - [11/Jan/2022:11:56:52 +0800] "POST /landing-page/verify/code HTTP/1.1" 403 0 12 ms

问题分析

请求路径为:【浏览器】==》【apache】==》【gateway】==》【后端 web 应用】

注意:【apache】会将接收到的 HTTPS 请求,转换为 HTTP 请求转发到【gateway】

跟踪 DefaultCorsProcessor 类的 process() 方法

package org.springframework.web.cors.reactive;

public class DefaultCorsProcessor implements CorsProcessor {
...
    public boolean process(@Nullable CorsConfiguration config, ServerWebExchange exchange) {
        ...
        // 1. 判断是否是跨域请求,如果不是直接返回 true
        if (!CorsUtils.isCorsRequest(request)) {
            return true;
        }
        ...
        return handleInternal(exchange, config, preFlightRequest);
    }

    protected boolean handleInternal(ServerWebExchange exchange,
            CorsConfiguration config, boolean preFlightRequest) {
        ...
        // 2. 获取请求头 Origin 的值
        String requestOrigin = request.getHeaders().getOrigin();
        String allowOrigin = checkOrigin(config, requestOrigin);

        // 4. 为 null 表示不满足条件,返回 403
        if (allowOrigin == null) {
            logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
            rejectRequest(response);
            return false;
        }
        ...
        return true;
    }

    // 3. 校验 requestOrigin 是否符合跨域的配置
    protected String checkOrigin(CorsConfiguration config, @Nullable String requestOrigin) {
        return config.checkOrigin(requestOrigin);
    }

    // 5. 设置 http 响应状态码为 403
    protected void rejectRequest(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.FORBIDDEN);
    }
...
}

public abstract class CorsUtils {
    // 跨域请求需要满足下面两个条件
    // 1. 包含 Origin 请求头
    // 2. request 的 schema:host:port 和 origin 一致
    public static boolean isCorsRequest(ServerHttpRequest request) {
        return request.getHeaders().containsKey(HttpHeaders.ORIGIN) && !isSameOrigin(request);
    }
}

解决方案

因为【apache】会把 HTTPS 协议请求转换为 HTTP 协议请求,而同源策略要求协议相同。

所以转化后的请求不再是同源请求,因此需要在 application.yml 文件中新增如下配置。

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: ["https://xxx-gamma.wumii.net"] # 此行配置是重点
            allowedMethods:
              - GET
              - POST
              - HEAD
              - PUT
              - DELETE
            allowedHeaders: '*'
            allowCredentials: true
        add-to-simple-url-handler-mapping: true

以支持跨域请求。

拓展阅读

GET 请求为什么不会自动加上 Origin 请求头?

因为:

以下情况浏览器会带上 Origin
1、所有跨域请求
2、除了 GETHEAD 请求外的同源请求

引用自 为什么有的请求,浏览器不会在请求头中自动加上 origin?

参考

  • Spring Cloud Gateway一次请求调用源码解析
  • SpringCloudGateway CORS方案看这篇就够了
  • 浏览器跨域限制概述

你可能感兴趣的:(【问题排查】Spring Cloud Gateway 返回 403 状态码)