【问题标题】:403 on POST preflight (sometimes)POST 预检时出现 403(有时)
【发布时间】:2018-03-14 22:02:25
【问题描述】:

我在 Nginx 反向代理后面有一个 Spring Boot 服务器,我使用来自 React 应用程序的 fetch 访问它。前端是从另一个端口提供的,所以我必须在服务器上启用 CORS。在大多数情况下,这很有效,但我的用户中约有 1% 收到 403 响应 OPTIONS 预检请求,我需要帮助找出原因。我遇到的最大问题是我无法在我的任何机器上复制该问题。

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 的 2 种类型的日志行:

"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"

表示运行 Chrome 61 的 Windows 7,并且

"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"

表示运行 IE11 的 Windows 7。

使用相同操作系统和浏览器设置的其他用户不会遇到任何问题。

使用 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

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

任何帮助将不胜感激。

【问题讨论】:

    标签: spring http nginx cors


    【解决方案1】:

    我终于找到了错误,这完全是我自己的错。

    从 Nginx access.log 中可以看出,其中一个失败的预检有一个 $http_referer,即 www.example.com/login 的 Origin Header。这当然会导致预检失败,因为我的 CORS 配置只允许 example.com没有 www 子域

    我已通过将server 块添加到 Nginx 配置来解决此问题,以便来自www 子域的所有请求都使用301 Permanently moved 重定向到非www 域:

    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 以保持所有内容的加密。

    这样我可以确保所有请求都使用https://example.com作为来源进入我的2台服务器,并且无需修改CORS配置。

    【讨论】:

      猜你喜欢
      • 2021-06-07
      • 2014-05-13
      • 2017-01-05
      • 1970-01-01
      • 2023-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多