X

spring boot应用在 POST请求的Invalid CORS request的问题

继上一篇遇到的Invalid CORS request之后,很久没有遇到这个问题了,今天又遇到了这个问题。上次问题是因为nginx没有把host信息代理给后面的java应用导致的,https://huster.top/htmls/511.html,而这次居然不是这个问题,检查了下 proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;该有的配置都有。

这就奇怪了,就是为什么呢?

现象: GET请求没有问题。POST请求下在http下是好的,在https报403 Invalid CORS request错误。刚开始,我并没有意识到是http和https的问题,而是集团有日常、预发环境。日常验证是http验证的,一切正常。上到预发就发生了这个问题,排查其他配置、差异什么的走了一些弯路。

排查过程:

1.当然是看access log. 首先看下access log里有没有什么线索。寻找access log发现的确是 403, 突然发现域名问题。access log记录的域名是http的,真实情况我们是https的。会不会是因为这个导致的跨域呢?在去看log_formate 发现http是写死的,好生失望

log_format proxyformat "$remote_addr $request_time_usec $http_x_readtime [$time_local] \"$request_method http://$host$request_uri\" $status $body_bytes_sent \"$http_referer\" \"$upstream_addr\" \"$http_user_agent\" \"$cookie_unb\" \"$cookie_cookie2\" \"$eagleeye_traceid\"";
  1. 接着检查nginx的host透传,也就是上次遇到的问题,配置也是对的,也没有问题
  2. 没有头绪了,只能断点调试了。但是本地没有,只能远程调试了。使用了ieda的远程调试功能,启动jvm的加上 -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=7878 就可以断点调试了。调试发现,还是集中在上次的类和方法里,主要是这方法

class :  DefaultCorsProcessor

method:  processRequest

 

/*** Handle the given request.
*/
    protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
            CorsConfiguration config, boolean preFlightRequest) throws IOException {

        String requestOrigin = request.getHeaders().getOrigin();
        String allowOrigin = checkOrigin(config, requestOrigin);

        HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
        List<HttpMethod> allowMethods = checkMethods(config, requestMethod);

        List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
        List<String> allowHeaders = checkHeaders(config, requestHeaders);

      if (allowOrigin == null || allowMethods == null || (preFlightRequest && allowHeaders == null)) {
            rejectRequest(response);
            return false;
        }

        HttpHeaders responseHeaders = response.getHeaders();
        responseHeaders.setAccessControlAllowOrigin(allowOrigin);
        responseHeaders.add(HttpHeaders.VARY, HttpHeaders.ORIGIN);

        if (preFlightRequest) {
            responseHeaders.setAccessControlAllowMethods(allowMethods);
        }

        if (preFlightRequest && !allowHeaders.isEmpty()) {
            responseHeaders.setAccessControlAllowHeaders(allowHeaders);
        }

        if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
            responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
        }

        if (Boolean.TRUE.equals(config.getAllowCredentials())) {
            responseHeaders.setAccessControlAllowCredentials(true);
        }

        if (preFlightRequest && config.getMaxAge() != null) {
            responseHeaders.setAccessControlMaxAge(config.getMaxAge());
        }

        response.flush();
        return true;
    }

 

 

最终走入到上面的方法, 注意红色部分,主要是因为allowOrigin为空了,那么看看String allowOrigin = checkOrigin(config, requestOrigin); 那么这个方法干了什么呢? 这个主要是读取我们的cors配置,看看配置域名里有没有http header 里origin传入的域名

 

@Configuration
public class CORS {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("http://localhost:9001",
                                "http://localhost:7001",
                                "http://127.0.0.1:9001",
                                "http://127.0.0.1:8080")
                        .allowedMethods("POST","GET","OPTIONS");
            }
        };
    }
}

 

我们配置的域名里并没有传入的origin,导致了allowOrigin为空,导致上面的报错。

于是,我把这个Configuration删掉,结果就好了,问题解决了

  1. 但是还是有几个问题一直萦绕在我脑海里,挥之不去。

a. 为什么GET可以,唯独POST不行

b. 为毛同一个域名,还会校验跨域配置? 也就是校验我们配置的域名列表??

c. 另外一个应用也有这个配置,为什么他就问题。

 

  1. 为什么GET可以,唯独POST有问题 ? 于是我接着调试 . 发现GET请求的时候,是在这句话这里返回的
ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
        if (WebUtils.isSameOrigin(serverRequest)) {
            logger.debug("Skip CORS processing: request is from same origin");
            return true;
        }

接着看isSameOrigin方法。
public static boolean isSameOrigin(HttpRequest request) {
        String origin = request.getHeaders().getOrigin();
        if (origin == null) {
            return true;
        }
        UriComponentsBuilder urlBuilder;
        if (request instanceof ServletServerHttpRequest) {
            // Build more efficiently if we can: we only need scheme, host, port for origin comparison
            HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
            urlBuilder = new UriComponentsBuilder().
                    scheme(servletRequest.getScheme()).
                    host(servletRequest.getServerName()).
                    port(servletRequest.getServerPort()).
                    adaptFromForwardedHeaders(request.getHeaders());
        }
        else {
            urlBuilder = UriComponentsBuilder.fromHttpRequest(request);
        }
        UriComponents actualUrl = urlBuilder.build();
        UriComponents originUrl = UriComponentsBuilder.fromOriginHeader(origin).build();
        return (ObjectUtils.nullSafeEquals(actualUrl.getHost(), originUrl.getHost()) &&
                getPort(actualUrl) == getPort(originUrl));
    }

 

通过调试发现,因为GET的时候并没有在header头里带origin,所以在方法的第一行就返回了。

为了验证我的想法,我在get的时候,也通过postman发送了header,结果出现了post一样的报错,证实了我的想法。

 

  1. 为什么相同的域名还会出现跨域的问题

还是isSameOrigin这个方法,通过调试发现origin里的域名是带了https的,而通过servletRequest.getScheme()发现,返回的是http的。再去查看nginx的配置

location / {
proxy_pass http://127.0.0.1:7001;
if you want to enable cell logic, you must change your proxy_pass conf to following
#proxy_pass $ups;
}

这里写死了http,原来如此。origin里发送的域名是 https://xx.xx.xx而我们转给java程序的是http://xx.xx.xx所以导致了跨域问题。

ps : 因为证书问题,公司的https是通过统一接入层,代理给下面的应用的,所以应用都是http的。而JavaScript是https的所以导致了问题。

至此,所以的谜团就解开了。要解决问题很容易了,删除你的cors配置,如果非得要,那么把http的域名加进去就可以了。

  1. 最后我去看了另外一应用,发现该应用把post都改成get了,可能当时遇到了同样的问题,但是没有去仔细解  ^_^
Categories: Java学习
龙安_任天兵: 不忘初心,方得始终!

View Comments (1)

  • 我看了你两篇cors的文章。
    第一篇是讲nginx转发时,没有配置originUrl,导致业务代码接收到的请求host丢失。
    第二篇是讲nginx转发时,没有配置好转发的请求协议,导致业务代码接受到的请求协议不一致。