403 禁止来自本地的 CORS 请求

403 Forbidden on CORS request from local

我正在使用 Gluu Server 并尝试通过 CORS/AJAX 请求从 /.well-known/openid-configuration 端点获取 OpenID Connect 配置(用于 Angular 应用程序) .但是,当我尝试通过 XHR 请求端点从本地托管的 app/HTML 文件请求端点时,我收到 403 Forbidden 错误。

这似乎只发生在请求来自本地上下文时,即 Angular 的开发服务器或请求端点的本地 HTML 文件。如果我打开在服务器上托管的执行 AJAX 请求的同一个 HTML 文件,它就可以工作。

测试 HTML 文件如下所示

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
    <div id="content"></div>
    <script type="text/javascript">
        var url = 'https://example.com/.well-known/openid-configuration';

        var req = new XMLHttpRequest();

        req.open('GET', url, true);
        req.setRequestHeader('Content-Type', 'application/json');

        req.onload = () => {
            if (req.status >= 200 && req.status < 400) {
                console.log('[XHR SUCCESS]');
                var el = document.getElementById('content');
                el.innerHTML = req.responseText;
            } else {
                console.log('[XHR ERROR]', req);
            }
        }

        req.onerror = () => {
            console.log('[XHR CONNECTION ERROR]');
        }
        req.send();
    </script>
</body>
</html>

从本地文件请求

如上所述,从本地 HTML 文件请求时,我收到 403 Forbidden 错误。

在浏览器控制台(Chrome),输出两个错误:

Failed to load resource: the server responded with a status of 403 (Forbidden)
Access to XMLHttpRequest at 'https://example.com/.well-known/openid-configuration' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

我发现服务器上与此相关的唯一输出在文件 /var/log/apache2/other_vhosts_access.log:

example.com:443 <IP> - - [11/Mar/2019:10:45:20 +0000] "OPTIONS /.well-known/openid-configuration HTTP/1.1" 403 3763 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"

当从本地请求时,服务器收到以下内容(来自 Apache 的 log_forensic 模块):

OPTIONS /.well-known/openid-configuration HTTP/1.1|Host:example.com|Connection:keep-alive|Pragma:no-cache|Cache-Control:no-cache|Access-Control-Request-Method:GET|Origin:null|User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36|Access-Control-Request-Headers:content-type|Accept:*/*|Accept-Encoding:gzip, deflate, br|Accept-Language:en-US,en;q=0.9

从 server-hosted 文件请求

当执行与上述完全相同的操作时,但 HTML 文件托管在服务器上,请求成功完成。

访问日志中的输出:

example.com:443 <IP> - - [11/Mar/2019:11:06:46 +0000] "OPTIONS /.well-known/openid-configuration HTTP/1.1" 200 779 "http://example.org/xhr-cors.html" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
example.com:443 <IP> - - [11/Mar/2019:11:06:46 +0000] "GET /.well-known/openid-configuration HTTP/1.1" 200 6629 "http://example.org/xhr-cors.html" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"

来自 log_forensic:

OPTIONS /.well-known/openid-configuration HTTP/1.1|Host:example.com|Connection:keep-alive|Access-Control-Request-Method:GET|Origin:http%3a//example.org|User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36|Access-Control-Request-Headers:content-type|Accept:*/*|Referer:http%3a//example.org/xhr-cors.html|Accept-Encoding:gzip, deflate, br|Accept-Language:en-US,en;q=0.9

GET /.well-known/openid-configuration HTTP/1.1|Host:example.com|Connection:keep-alive|Origin:http%3a//example.org|User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36|Content-Type:application/json|Accept:*/*|Referer:http%3a//example.org/xhr-cors.html|Accept-Encoding:gzip, deflate, br|Accept-Language:en-US,en;q=0.9

Apache 配置

服务器上 Apache 的配置是

<VirtualHost  *:80>
        ServerName example.com
        Redirect  / https://example.com/
        DocumentRoot "/var/www/html/"
RewriteEngine on
RewriteCond %{SERVER_NAME} =example.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

<VirtualHost *:443>
        DocumentRoot "/var/www/html/"
        ServerName example.com:443

        LogLevel warn
        SSLEngine on
        SSLProtocol -all +TLSv1.1 +TLSv1.2
        SSLHonorCipherOrder On
        SSLCipherSuite ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:DHE-RSA-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK

#               SetEnv proxy-nokeepalive 1
        SetEnv proxy-initial-not-pooled 1
        Timeout 60
                ProxyTimeout 60

        # Security headers
#        Header always append X-Frame-Options SAMEORIGIN
                Header always set X-Xss-Protection "1; mode=block"
                Header always set X-Content-Type-Options nosniff
#        Header always set Content-Security-Policy "default-src 'self' 'unsafe-inline' https://example.com"
        Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

        Header edit Set-Cookie ^((?!session_state).*)$ ;HttpOnly
        SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0

                # Unset X-ClientCert to make sure that we not get certificate in request
        RequestHeader unset X-ClientCert

                # Turn off support for true Proxy behaviour as we are acting as a transparent proxy
        ProxyRequests Off

                # Turn off VIA header as we know where the requests are proxied
        ProxyVia Off

                # Turn on Host header preservation so that the servlet container
                # can write links with the correct host and rewriting can be avoided.
        ProxyPreserveHost On

                # Preserve the scheme when proxying the request to Jetty
                RequestHeader set X-Forwarded-Proto "https" env=HTTPS

                Header unset ETag
        FileETag None

        RedirectMatch ^(/)$ /identity/

                # Set the permissions for the proxy
                <Proxy *>
                  AddDefaultCharset off
                  Order deny,allow
                  Allow from all
                </Proxy>

        <Location /oxauth>
                ProxyPass http://localhost:8081/oxauth retry=5 connectiontimeout=60 timeout=60
#                Header set Access-Control-Allow-Origin "*"
                Order deny,allow
                Allow from all
        </Location>

        <LocationMatch /oxauth/auth/cert/cert-login>
            SSLVerifyClient optional_no_ca
            SSLVerifyDepth 10
            SSLOptions -StdEnvVars +ExportCertData

                        # Forward certificate to destination server
            RequestHeader set X-ClientCert %{SSL_CLIENT_CERT}s
        </LocationMatch>

        <Location /idp>
                ProxyPass http://localhost:8086/idp retry=5 connectiontimeout=60 timeout=60
                Order deny,allow
                Allow from all
        </Location>

        <Location /identity>
                ProxyPass http://localhost:8082/identity retry=5 connectiontimeout=60 timeout=60
                Order deny,allow
                Allow from all
        </Location>

        <Location /cas>
                ProxyPass http://localhost:8083/cas retry=5 connectiontimeout=60 timeout=60
                Order deny,allow
                Allow from all
        </Location>

        <Location /oxauth-rp>
                ProxyPass http://localhost:8085/oxauth-rp retry=5 connectiontimeout=60 timeout=60
                Order deny,allow
                Allow from all
        </Location>

        <Location /asimba>
                ProxyPass http://localhost:8084/asimba retry=5 connectiontimeout=60 timeout=60
                Order deny,allow
                Allow from all
        </Location>

        <Location /passport>
                ProxyPass http://localhost:8090/passport retry=5 connectiontimeout=60 timeout=60
                Order deny,allow
                Allow from all
        </Location>

        <Location /casa>
                ProxyPass http://localhost:8091/casa retry=5 connectiontimeout=60 timeout=60
                Order deny,allow
                Allow from all
        </Location>

       <LocationMatch "/.well-known/openid-configuration">
               ProxyPass http://localhost:8081/oxauth/.well-known/openid-configuration
               Header set Access-Control-Allow-Origin "*"
       </LocationMatch>

#                        ProxyPass /.well-known/openid-configuration http://localhost:8081/oxauth/.well-known/openid-configuration
ProxyPass /.well-known/simple-web-discovery http://localhost:8081/oxauth/.well-known/simple-web-discovery
ProxyPass        /.well-known/webfinger http://localhost:8081/oxauth/.well-known/webfinger
        ProxyPass        /.well-known/uma2-configuration http://localhost:8081/oxauth/restv1/uma2-configuration
        ProxyPass        /.well-known/fido-configuration http://localhost:8081/oxauth/restv1/fido-configuration
        ProxyPass        /.well-known/fido-u2f-configuration http://localhost:8081/oxauth/restv1/fido-configuration
        ProxyPass        /.well-known/scim-configuration http://localhost:8082/identity/restv1/scim-configuration
        ServerAlias        example.com
        SSLCertificateFile        /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>

我已经注释掉了

ProxyPass /.well-known/openid-configuration http://localhost:8081/oxauth/.well-known/openid-configuration

指令并引入了

<LocationMatch "/.well-known/openid-configuration">
        ProxyPass http://localhost:8081/oxauth/.well-known/openid-configuration
        Header set Access-Control-Allow-Origin "*"
</LocationMatch>

添加 CORS header(s) 的指令。

其他

我试图找出问题所在的其他事情:

我非常感谢对此的一些意见,因为它让我很困惑,并且在开发很麻烦时无法在本地工作。如果需要任何说明,请告诉我。

原来这个问题是两个不相关的问题的合并。

首先,这主要是猜测,似乎 Chrome 阻止了来自本地文件(HTML 文件)的请求并仅提供输出也就是说,对我来说,非常混乱。 IE。 403 错误 可能 是因为 Chrome 以某种方式阻止了 CORS 请求。我尝试了 运行 Chrome 各种标志,例如--disable-web-security--allow-file-access-from-files,但这并没有改变本地 HTML 文件的输出。所以,本地文件请求还是失败了,具体原因我也不太清楚。但是,由于这只是为了测试,所以目前对我来说并没有那么重要。

其次,Angular 项目中拦截器的错误实现覆盖了所有 headers 请求。修复此问题后,本地服务器能够请求端点。

碰巧这两个不同问题的输出看起来几乎相同,这让我很失望。