我在Nginx反向代理后面有一个Spring Boot服务器,我使用React应用程序中的fetch访问。前端是从另一个端口提供的,所以我必须在服务器上启用CORS。在大多数情况下,这种方法效果很好,但是大约1%的用户在响应OPTIONS预检请求时得到403,我需要帮助找出原因。我遇到的最大问题是我无法在任何机器上复制问题。
Spring Boot CORS配置:
@Bean
public FilterRegistrationBean corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("https://example.com");
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
Nginx配置(3000是NodeJS服务前端,3001是Spring Boot):
server {
...
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location /api/v1/ {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
...
}
Nginx日志格式(为清楚起见,我删除了一些部分):
"$request" $status "$http_referer" "$http_user_agent"
通过查看Nginx access.log,我已经确定了两种类型的日志行,其中403显示:
"OPTIONS /api/v1/oauth/token?grant_type=password&username=user%40example.com&password=****" 403 "https://www.example.com/login" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36"
表示Windows 7运行Chrome 61,
"OPTIONS /api/v1/oauth/token?grant_type=password&username=user%40example.com&password=****" 403 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
意味着Windows 7运行IE11。
使用相同操作系统和浏览器设置的其他用户没有遇到任何问题。
使用fetch手动发送数据:
URL: https://example.com:3001/api/v1/oauth/token?grant_type=password&username=user%40example.com&password=****
Method: POST
Headers:
Authorization: Basic XXXXXXXXXXX=
Content-Type: application/x-www-form-urlencoded
Body: undefined
工作用户的预检请求中的实际参数(来自Chrome控制台):
请求标题:
OPTIONS /api/v1/oauth/token?grant_type=password&username=user%40example.com&password=**** HTTP/1.1
Host: https://example.com:3001
Connection: keep-alive
Access-Control-Request-Method: POST
Origin: https://example.com:3000
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36
Access-Control-Request-Headers: authorization
Accept: */*
Referer: https://example.com:3000/login
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8,sv;q=0.6
回复标题:
HTTP/1.1 200
Access-Control-Allow-Origin: https://example.com:3000
Vary: Origin
Access-Control-Allow-Methods: GET,PUT,POST,DELETE,PATCH
Access-Control-Allow-Headers: authorization
Content-Length: 0
Date: Tue, 03 Oct 2017 16:01:37 GMT
我的猜测是我发送获取请求的方式有问题,或者我错误地配置了标头。但到目前为止,我还没能解决它。
任何帮助都会非常受欢迎。
答案 0 :(得分:1)
我终于找到了错误,完全是我自己的错。
从Nginx access.log中可以看出,其中一个失败的预检有一个$http_referer
,即www.example.com/login
的起始标题。这当然会导致预检失败,因为我的CORS配置只允许example.com
,而没有www
子域。
我已通过向Nginx配置添加server
块来解决此问题,以便来自www
子域的所有请求都使用{{{}返回到非www
域的重定向1}}:
301 Permanently moved
请注意,我还会将所有server {
...
listen 443 ssl;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location /api/v1/ {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
...
}
server {
server_name www.example.com example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
流量重定向到http
,以便对所有内容进行加密。
通过这种方式,我可以确保所有请求都以https
为原点进入我的2台服务器,并且无需修改CORS配置。