PHP/Apache2 的响应中缺少 CORS headers
Missing CORS headers in response from PHP/Apache2
我公司正在进行的 Zend Expressive 项目已准备好交付,但在我们的暂存环境中,我们似乎缺少对 CORS pre-flight 请求的响应 headers。这不会发生在我们的开发环境中。我们在管道中使用了 CorsMiddleware,但看起来中间件并不是罪魁祸首。
问题
在运行时,中间件会检测传入的 pre-flight 请求,它会像这样回复:
HTTP/1.1 200 OK
Date: Mon, 20 Aug 2018 15:09:03 GMT
Server: Apache
X-Powered-By: PHP/7.1.19
Access-Control-Allow-Origin: https://example.com
Vary: Origin
Access-Control-Allow-Headers: content-type
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
嗯,这只适用于我们的开发服务器和 php 的 built-in 网络服务器。响应与我们的登台服务器不同,即使请求完全相同,除了主机:
HTTP/1.1 200 OK
Date: Mon, 20 Aug 2018 15:11:29 GMT
Server: Apache
Keep-Alive: timeout=5, max=100
Cache-Control: max-age=0, no-cache
Content-Length: 0
Content-Type: text/html; charset=UTF-8
我们的尝试
调查中间件
我们已经验证 CorsMiddleware 运行良好并且实际设置了所需的 headers。当我们修改 CorsMiddleware 的响应代码并将其设置为 202
而不是 200
时,我们现在 do 得到我们正在寻找的 headers。将响应代码改回 200
会使 headers 再次消失。
手动设置headers
使用以下示例:
header('Access-Control-Allow-Origin: https://example.com');
header('Access-Control-Allow-Headers: content-type');
header('Vary: Origin');
exit(0);
这具有相同的行为,直到我们将响应代码修改为 204
或 200
以外的任何其他代码。
正在查看body
响应 body 是空的,不应包含任何内容,但是当我们向响应 body 添加内容时,headers 似乎没有任何错误。
因此,如果我添加 body 内容,则会出现 headers。没有body内容?没有 CORS headers。这是 Apache 中的一些设置吗?我是否缺少 PHP 中的某些配置?我是不是忘记了什么?
更多详情
所有请求均已通过 httpie、Postman、curl 和 PhpStorm 的 http 客户端进行测试。
这是 httpie 示例:
http -v OPTIONS https://staging.****.com \
'access-control-request-method:POST' \
'origin:https://example.com' \
'access-control-request-headers:content-type'
这是 curl 示例:
curl "https://staging.****.com" \
--request OPTIONS \
--include \
--header "access-control-request-method: POST" \
--header "origin: https://example.com" \
--header "access-control-request-headers: content-type"
pipeline.php中的Cors配置(通配符仅用于测试):
$app->pipe(new CorsMiddleware([
"origin" => [
"*",
],
"headers.allow" => ['Content-Type'],
"headers.expose" => [],
"credentials" => false,
"cache" => 0,
// Get list of allowed methods from matched route or provide empty array.
'methods' => function (ServerRequestInterface $request) {
$result = $request->getAttribute(RouteResult::class);
/** @var \Zend\Expressive\Router\Route $route */
$route = $result->getMatchedRoute();
return $route ? $route->getAllowedMethods() : [];
},
// Respond with a json response containing the error message when the CORS check fails.
'error' => function (
ServerRequest $request,
Response $response,
$arguments
) {
$data['status'] = 'error';
$data['message'] = $arguments['message'];
return $response->withHeader('Content-Type', 'application/json')
->getBody()->write(json_encode($data));
},
]);
暂存环境:
OS: Debian 9.5 server
Webserver: Apache/2.4.25 (Debian) (built: 2018-06-02T08:01:13)
PHP: PHP 7.1.20-1+0~20180725103315.2+stretch~1.gbpd5b650 (cli) (built: Jul 25 2018 10:33:20) ( NTS )
暂存 Apache2 虚拟主机:
<IfModule mod_ssl.c>
<VirtualHost ****:443>
ServerName staging.****.com
DocumentRoot /var/www/com.****.staging/public
ErrorLog /var/log/apache2/com.****.staging.error.log
CustomLog /var/log/apache2/com.****.staging.access.log combined
<Directory /var/www/com.****.staging>
Options +SymLinksIfOwnerMatch
AllowOverride All
Order allow,deny
allow from all
</Directory>
SSLCertificateFile /etc/letsencrypt/live/staging.****.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/staging.****.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
开发中的 Apache2 虚拟主机:
<VirtualHost *:443>
ServerName php71.****.com
ServerAdmin dev@****.com
DocumentRoot /var/www/
<Directory /var/www/>
Options Indexes FollowSymlinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.ssl.log
CustomLog ${APACHE_LOG_DIR}/access.ssl.log combined
SSLEngine On
SSLCertificateFile /etc/ssl/certs/****.crt
SSLCertificateKeyFile /etc/ssl/certs/****.key
</VirtualHost>
对于每个body 指责 Cloudflare 的人:
使用 httpie 直接 link 试试这个。 link 没有使用 cloudflare:
http -v OPTIONS http://37.97.135.33/cors.php \
'access-control-request-method:POST' \
'origin:https://example.com' \
'access-control-request-headers:content-type'
在浏览器中查看源代码:http://37.97.135.33/cors.php?source=1
请确保您在 Zend Expressive 中的配置正确。例如下面的代码将允许 CORS 访问任何调用域
use Psr\Http\Message\ServerRequestInterface;
use Tuupola\Middleware\CorsMiddleware;
use Zend\Expressive\Router\RouteResult;
$app->pipe(new CorsMiddleware([
"origin" => ["*"],
"methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"]
}
]));
您的配置清楚地表明 headers 确实会生成,所以这不是代码或中间件的错。
我相信 headers 会被某些东西删除 - 检查 Apache 的 mod_headers
和配置,以防存在流氓 unset
指令。
另一种不太可能的可能性是,您正在通过负载均衡器或某种代理查看登台服务器,这会重写 headers 并将 CORS 排除在外(为了验证这一点,您可能需要拦截 Apache 的传出流量)。
这两个错误我自己都犯过。
从我在这里读到的所有内容来看,包括您的评论,您的 "production" 服务器似乎在代理服务器后面,更确切地说是 CloudFlare。您提供了有关工作开发环境的详细信息,但没有提供有关 non-working 生产环境的详细信息。
您的设置似乎是正确的,如果它在没有 PROXY 的开发设置上确实有效,则意味着 PROXY 正在 mod 验证 headers。
关于 CloudFlare 的快速搜索已经给出足够的迹象表明 CloudFlare 可能是您问题的原因。
我强烈建议您在 CloudFlare 中启用 "Development Mode",这样它会绕过缓存,您可以看到 coming/going 到原始服务器的所有内容。
以下文章应该可以帮助您了解和解决您的问题:
https://support.cloudflare.com/hc/en-us/articles/203063414-Why-can-t-I-see-my-CORS-headers-
更新:
您的问题似乎来自 Apache Mod Pagespeed,将其关闭后,您的 headers 会一直存在。
目前还不清楚为什么 mod 正在剥离你的 headers,但这是另一个问题和时间。
我公司正在进行的 Zend Expressive 项目已准备好交付,但在我们的暂存环境中,我们似乎缺少对 CORS pre-flight 请求的响应 headers。这不会发生在我们的开发环境中。我们在管道中使用了 CorsMiddleware,但看起来中间件并不是罪魁祸首。
问题
在运行时,中间件会检测传入的 pre-flight 请求,它会像这样回复:
HTTP/1.1 200 OK
Date: Mon, 20 Aug 2018 15:09:03 GMT
Server: Apache
X-Powered-By: PHP/7.1.19
Access-Control-Allow-Origin: https://example.com
Vary: Origin
Access-Control-Allow-Headers: content-type
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
嗯,这只适用于我们的开发服务器和 php 的 built-in 网络服务器。响应与我们的登台服务器不同,即使请求完全相同,除了主机:
HTTP/1.1 200 OK
Date: Mon, 20 Aug 2018 15:11:29 GMT
Server: Apache
Keep-Alive: timeout=5, max=100
Cache-Control: max-age=0, no-cache
Content-Length: 0
Content-Type: text/html; charset=UTF-8
我们的尝试
调查中间件
我们已经验证 CorsMiddleware 运行良好并且实际设置了所需的 headers。当我们修改 CorsMiddleware 的响应代码并将其设置为 202
而不是 200
时,我们现在 do 得到我们正在寻找的 headers。将响应代码改回 200
会使 headers 再次消失。
手动设置headers
使用以下示例:
header('Access-Control-Allow-Origin: https://example.com');
header('Access-Control-Allow-Headers: content-type');
header('Vary: Origin');
exit(0);
这具有相同的行为,直到我们将响应代码修改为 204
或 200
以外的任何其他代码。
正在查看body
响应 body 是空的,不应包含任何内容,但是当我们向响应 body 添加内容时,headers 似乎没有任何错误。
因此,如果我添加 body 内容,则会出现 headers。没有body内容?没有 CORS headers。这是 Apache 中的一些设置吗?我是否缺少 PHP 中的某些配置?我是不是忘记了什么?
更多详情
所有请求均已通过 httpie、Postman、curl 和 PhpStorm 的 http 客户端进行测试。
这是 httpie 示例:
http -v OPTIONS https://staging.****.com \
'access-control-request-method:POST' \
'origin:https://example.com' \
'access-control-request-headers:content-type'
这是 curl 示例:
curl "https://staging.****.com" \
--request OPTIONS \
--include \
--header "access-control-request-method: POST" \
--header "origin: https://example.com" \
--header "access-control-request-headers: content-type"
pipeline.php中的Cors配置(通配符仅用于测试):
$app->pipe(new CorsMiddleware([
"origin" => [
"*",
],
"headers.allow" => ['Content-Type'],
"headers.expose" => [],
"credentials" => false,
"cache" => 0,
// Get list of allowed methods from matched route or provide empty array.
'methods' => function (ServerRequestInterface $request) {
$result = $request->getAttribute(RouteResult::class);
/** @var \Zend\Expressive\Router\Route $route */
$route = $result->getMatchedRoute();
return $route ? $route->getAllowedMethods() : [];
},
// Respond with a json response containing the error message when the CORS check fails.
'error' => function (
ServerRequest $request,
Response $response,
$arguments
) {
$data['status'] = 'error';
$data['message'] = $arguments['message'];
return $response->withHeader('Content-Type', 'application/json')
->getBody()->write(json_encode($data));
},
]);
暂存环境:
OS: Debian 9.5 server
Webserver: Apache/2.4.25 (Debian) (built: 2018-06-02T08:01:13)
PHP: PHP 7.1.20-1+0~20180725103315.2+stretch~1.gbpd5b650 (cli) (built: Jul 25 2018 10:33:20) ( NTS )
暂存 Apache2 虚拟主机:
<IfModule mod_ssl.c>
<VirtualHost ****:443>
ServerName staging.****.com
DocumentRoot /var/www/com.****.staging/public
ErrorLog /var/log/apache2/com.****.staging.error.log
CustomLog /var/log/apache2/com.****.staging.access.log combined
<Directory /var/www/com.****.staging>
Options +SymLinksIfOwnerMatch
AllowOverride All
Order allow,deny
allow from all
</Directory>
SSLCertificateFile /etc/letsencrypt/live/staging.****.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/staging.****.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
开发中的 Apache2 虚拟主机:
<VirtualHost *:443>
ServerName php71.****.com
ServerAdmin dev@****.com
DocumentRoot /var/www/
<Directory /var/www/>
Options Indexes FollowSymlinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.ssl.log
CustomLog ${APACHE_LOG_DIR}/access.ssl.log combined
SSLEngine On
SSLCertificateFile /etc/ssl/certs/****.crt
SSLCertificateKeyFile /etc/ssl/certs/****.key
</VirtualHost>
对于每个body 指责 Cloudflare 的人:
使用 httpie 直接 link 试试这个。 link 没有使用 cloudflare:
http -v OPTIONS http://37.97.135.33/cors.php \
'access-control-request-method:POST' \
'origin:https://example.com' \
'access-control-request-headers:content-type'
在浏览器中查看源代码:http://37.97.135.33/cors.php?source=1
请确保您在 Zend Expressive 中的配置正确。例如下面的代码将允许 CORS 访问任何调用域
use Psr\Http\Message\ServerRequestInterface;
use Tuupola\Middleware\CorsMiddleware;
use Zend\Expressive\Router\RouteResult;
$app->pipe(new CorsMiddleware([
"origin" => ["*"],
"methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"]
}
]));
您的配置清楚地表明 headers 确实会生成,所以这不是代码或中间件的错。
我相信 headers 会被某些东西删除 - 检查 Apache 的 mod_headers
和配置,以防存在流氓 unset
指令。
另一种不太可能的可能性是,您正在通过负载均衡器或某种代理查看登台服务器,这会重写 headers 并将 CORS 排除在外(为了验证这一点,您可能需要拦截 Apache 的传出流量)。
这两个错误我自己都犯过。
从我在这里读到的所有内容来看,包括您的评论,您的 "production" 服务器似乎在代理服务器后面,更确切地说是 CloudFlare。您提供了有关工作开发环境的详细信息,但没有提供有关 non-working 生产环境的详细信息。
您的设置似乎是正确的,如果它在没有 PROXY 的开发设置上确实有效,则意味着 PROXY 正在 mod 验证 headers。
关于 CloudFlare 的快速搜索已经给出足够的迹象表明 CloudFlare 可能是您问题的原因。
我强烈建议您在 CloudFlare 中启用 "Development Mode",这样它会绕过缓存,您可以看到 coming/going 到原始服务器的所有内容。
以下文章应该可以帮助您了解和解决您的问题:
https://support.cloudflare.com/hc/en-us/articles/203063414-Why-can-t-I-see-my-CORS-headers-
更新:
您的问题似乎来自 Apache Mod Pagespeed,将其关闭后,您的 headers 会一直存在。
目前还不清楚为什么 mod 正在剥离你的 headers,但这是另一个问题和时间。