继上一篇遇到的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 allowMethods = checkMethods(config, requestMethod);
List requestHeaders = getHeadersToUse(request, preFlightRequest);
List 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了,可能当时遇到了同样的问题,但是没有去仔细解 ^_^
