继上一篇遇到的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\"";
- 接着检查nginx的host透传,也就是上次遇到的问题,配置也是对的,也没有问题
- 没有头绪了,只能断点调试了。但是本地没有,只能远程调试了。使用了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删掉,结果就好了,问题解决了
- 但是还是有几个问题一直萦绕在我脑海里,挥之不去。
a. 为什么GET可以,唯独POST不行
b. 为毛同一个域名,还会校验跨域配置? 也就是校验我们配置的域名列表??
c. 另外一个应用也有这个配置,为什么他就问题。
- 为什么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一样的报错,证实了我的想法。
- 为什么相同的域名还会出现跨域的问题
还是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的域名加进去就可以了。
- 最后我去看了另外一应用,发现该应用把post都改成get了,可能当时遇到了同样的问题,但是没有去仔细解 ^_^
我看了你两篇cors的文章。
第一篇是讲nginx转发时,没有配置originUrl,导致业务代码接收到的请求host丢失。
第二篇是讲nginx转发时,没有配置好转发的请求协议,导致业务代码接受到的请求协议不一致。