有关POST预检的403(有时)

时间:2017-10-03 16:17:41

标签: spring http nginx cors

我在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

我的猜测是我发送获取请求的方式有问题,或者我错误地配置了标头。但到目前为止,我还没能解决它。

任何帮助都会非常受欢迎。

1 个答案:

答案 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配置。