HTTP 请求:可以在浏览器和 REST 客户端中看到 JSON 响应,但在浏览器中看不到

HTTP Request: Can see JSON response in browser and REST Client but not Browser

我试图在 Nginx 控制的子域上访问我自己的端点,我预计请求会失败并且 return 一个 JSON 有效负载如下:

{
    "hasError": true,
    "data": null,
    "error": {
        "statusCode": 401,
        "statusDescription": null,
        "message": "Could not find Session Reference in Request Headers"
    }
}

当我在浏览器中发出此请求时,它 return 在网络工具 (Brave Browser) 中出现 401:

控制台中出现此错误:

Access to fetch at 'https://services.mfwebdev.net/api/authentication/validate-session' from origin 'https://mfwebdev.net' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

当我在浏览器中点击有问题的 URL 时,我看到了正确的 JSON 响应,如果我在像失眠一样的 REST 客户端中点击 URL,我可以看到JSON 响应。

浏览器发送的 header 是:

:authority: services.mfwebdev.net
:method: GET
:path: /api/authentication/validate-session
:scheme: https
accept: application/json, text/plain, */*
accept-encoding: gzip, deflate, br
accept-language: en-GB,en-US;q=0.9,en;q=0.8
origin: https://mfwebdev.net
referer: https://mfwebdev.net/
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: same-site
sec-gpc: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36

我实际上也在 REST 客户端中使用了这些 header,我仍然可以看到正确的 JSON 结果。

请求(在代码中)如下(使用Angular):

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiResponse } from 'src/app/api/types/response/api-response.class';
import { ISessionApiService } from 'src/app/api/types/session/session-api-service.interface';
import { SessionResponse } from 'src/app/api/types/session/session-response.class';
import { environment } from '../../../environments/environment';

@Injectable()
export class SessionApiService implements ISessionApiService {

    private readonly _http: HttpClient;

    constructor(http: HttpClient) {
        this._http = http;
    }

    public createSession(): Observable<ApiResponse<SessionResponse>> {
        return this._http.post<ApiResponse<SessionResponse>>(`${environment.servicesApiUrl}/authentication/authorise`, {
            reference: environment.applicationReference,
            applicationName: environment.applicationName,
            referrer: environment.applicationReferrer
         });
    }

    public validateSession(): Observable<ApiResponse<boolean>> {
        return this._http.get<ApiResponse<boolean>>(`${environment.servicesApiUrl}/authentication/validate-session`);
    }
}

有人可以帮忙吗,我在这里完全不知所措。

编辑!!对于可能遇到此问题的使用 NginX 的任何人。问题出在我的 nginx.conf 文件中。我留下了我的(现在正在工作)server-side 配置的示例。

它不起作用的原因是,如果 OPTIONS 请求通过,我没有费心实际处理请求。

我现在处理每个请求类型(或将)并将 ACCESS-CONTROL-ALLOW-ORIGIN header 附加到请求中。

user root;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {}
http {
    include        /etc/nginx/proxy.conf;
    limit_req_zone $binary_remote_addr zone=one:10m rate=5r/s;
    server_tokens  off;

    sendfile on;
    # Adjust keepalive_timeout to the lowest possible value that makes sense
    # for your use case.
    keepalive_timeout   1000;
    client_body_timeout 1000;
    client_header_timeout 10;
    send_timeout 10;

    upstream upstreamExample{
        server 127.0.0.1:5001;
    }

    server {
        listen                    443 ssl http2;
        listen                    [::]:443 ssl http2;
        server_name               example.net *.example.net;
        ssl_certificate           /etc/letsencrypt/live/example.net/cert.pem;
        ssl_certificate_key       /etc/letsencrypt/live/example.net/privkey.pem;
        ssl_session_timeout       1d;
        ssl_protocols             TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers off;
        ssl_ciphers               ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
        ssl_session_cache         shared:SSL:10m;
        ssl_session_tickets       off;
        ssl_stapling              off;

        location / {
            if ($request_method = 'OPTIONS') {
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                add_header 'Access-Control-Max-Age' 1728000;
                add_header 'Content-Type' 'text/plain; charset=utf-8';
                add_header 'Content-Length' 0;
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
                add_header 'Access-Control-Allow-Headers' '*';

                return 204;
            }

            if ($request_method = 'POST') {
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                add_header 'Access-Control-Max-Age' 1728000;
                add_header 'Access-Control-Allow-Origin' '*' always;
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
                add_header 'Access-Control-Allow-Headers' '*';
            }

            if ($request_method = 'GET') {
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                add_header 'Access-Control-Max-Age' 1728000;
                add_header 'Access-Control-Allow-Origin' '*' always;
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
                add_header 'Access-Control-Allow-Headers' '*';
            }

            if ($request_method = 'DELETE') {
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                add_header 'Access-Control-Max-Age' 1728000;
                add_header 'Access-Control-Allow-Origin' '*' always;
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
                add_header 'Access-Control-Allow-Headers' '*';
            }

            proxy_pass https://upstreamExample;
            limit_req  zone=one burst=10 nodelay;
        }
    }
}

您需要通过发送适当的响应 header 来启用 CORS(Cross-Origin 资源共享),其中之一是 Access-Control-Allow-Origin header,它告诉浏览器所有来源都可以访问该资源。

CORS 策略是浏览器强加的一种安全措施,而不是由 Insomnia/Postman 等 REST 客户端强加的。因此 HTTP 请求在失眠中有效,但在浏览器中无效。

来自 MDN:

For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts. For example, XMLHttpRequest and the Fetch API follow the same-origin policy. This means that a web application using those APIs can only request resources from the same origin the application was loaded from unless the response from other origins includes the right CORS headers.

对子域的 HTTP 请求不属于 same-origin 策略,因此您需要启用 CORS。

资源: