本文概述
当有人使用Javascript(AJAX)请求你的Symfony项目的端点(通常(但不一定)是API)时, 将在客户端发现并报告此错误。在大多数情况下, 此错误无法在客户端解决, 因为该错误实际上是由服务器引起的, 而这又不是错误而是”安全措施”。
此安全措施是”同源来源”策略, 该策略确定网络浏览器允许第一个网页(www.myweb.com/page1.html)中包含的脚本访问第二个网页(www.myweb.com)中的数据/script.js), 但前提是两个网页的来源相同。源定义为URI方案(http://或https://等), 主机名(www.domain.com)和端口号(通常为端口80)的组合。简而言之, 这意味着要创建对网站A的请求, 我们需要从同一网站A发送请求, 如果你从网站B发送请求, 则该策略将适用, 并且你会在控制台中找到错误。
这项政策在某种程度上是多余的, 因为如果你的项目需要与第三方网站共享某些信息怎么办?为了解决此问题, 我们在服务器中使用了CORS规范。跨域资源共享(CORS)是一项允许跨域边界真正开放访问的规范。因此, 如果你提供公共内容, 则需要考虑(有时……你需要)使用CORS对其进行开放以实现通用JavaScript /浏览器访问。你可以在此处阅读有关CORS的更多信息。
如果你使用以下代码从另一个网站(https://fiddle.jshell.net)使用Javascript从浏览器执行XMLHttpRequest到你的应用程序(https:// sandbox / api)的端点, 请执行以下操作:
$.getJSON("https://sandbox/api", function(data){
console.log(data);
});
你将在控制台中收到以下错误消息:
XMLHttpRequest无法加载https:// sandbox / api。所请求的资源上不存在” Access-Control-Allow-Origin”标头。因此, 不允许访问源” https://fiddle.jshell.net”。
通常, 在PHP中, 你可以通过实现以下标头在脚本中启用CORS:
<?php
header("Access-Control-Allow-Origin: *");
*表示允许所有域访问服务器中脚本的响应。你只能将1个域设置为值, 否则, 以后你会遇到更多麻烦, 此外, 如果你需要添加对多个域的支持, 请在Stack Overflow上检查此问题。
但是, 当你使用symfony时, 就不会这样做。相反, 你需要在控制器中修改返回的响应。
解决控制器中的响应
在本示例中, 使用控制器模型, 我们将使用一个简单的控制器, 该控制器生成错误(没有标题)并返回一个简单的JSON响应:
<?php
namespace sandbox\mainBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class DefaultController extends Controller
{
/**
* The access point to this url will be:
* https://sandbox/api
*/
public function apiAction(){
$response = new Response();
$date = new \DateTime();
$response->setContent(json_encode([
'id' => uniqid(), 'time' => $date->format("Y-m-d")
]));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
为了解决这个问题, 我们需要修改响应并添加Access-Control-Allow-Origin标头:
<?php
public function apiAction(){
$response = new Response();
$date = new \DateTime();
$response->setContent(json_encode([
'id' => uniqid(), 'time' => $date->format("Y-m-d")
]));
$response->headers->set('Content-Type', 'application/json');
// Allow all websites
$response->headers->set('Access-Control-Allow-Origin', '*');
// Or a predefined website
//$response->headers->set('Access-Control-Allow-Origin', 'https://jsfiddle.net/');
// You can set the allowed methods too, if you want //$response->headers->set('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, PATCH, OPTIONS');
return $response;
}
origin参数指定可以访问资源的URI。浏览器必须执行此操作。对于没有凭据的请求, 服务器可以将” *”指定为通配符, 从而允许任何源访问资源。
解决静态文件和已实现的API
但是, 如果你改为处理静态文件, 或者已经拥有庞大的内置API怎么办?例如:
1)使用文件:如果你的symfony项目(在域A中)的Web目录(在资源文件夹中)中有一个文件(myfile.txt), 并且你想使用AJAX从域B请求该文件:
$.get("https://sandbox/resources/myfile.txt", function(data){
console.log(data);
});
2)使用已经建立的API:假设你已经使用FOSRestBundle建立了Restful API:
<?php
namespace AppBundle\Controller;
class UsersController
{
public function copyUserAction($id) // RFC-2518
{} // "copy_user" [COPY] /users/{id}
public function propfindUserPropsAction($id, $property) // RFC-2518
{} // "propfind_user_props" [PROPFIND] /users/{id}/props/{property}
public function proppatchUserPropsAction($id, $property) // RFC-2518
{} // "proppatch_user_props" [PROPPATCH] /users/{id}/props/{property}
// AND A LOT OF FUNCTIONS MORE :(
}
在两种情况下, 你都会在控制台中发现相同的” XMLHttpRequest无法加载”错误, 因此你需要在每个响应中添加提到的标头。但是, 在每个控制器中修改响应, 甚至使用纯PHP而不是ngix返回文件都会适得其反, 而且效率很低。因此, 为了以正确, 简单的方式实现此目标, 我们将依赖NelmioCorsBundle。 NelmioCorsBundle允许你使用ACL样式的每个URL配置发送跨域资源共享标头。
要安装NelmioCorsBundle, 请在composer中执行以下命令:
composer require nelmio/cors-bundle
或在composer.json文件中添加以下行, 然后执行composer install:
{
"require": {
"nelmio/cors-bundle": "^1.4"
}
}
然后继续使用registerBundles方法在AppKernel文件(app / AppKernel.php)中注册捆绑软件:
<?php
public function registerBundles()
{
$bundles = [
///..///
new Nelmio\CorsBundle\NelmioCorsBundle(), ///..///
];
///..///
}
最后, 继续进行所需的配置, 以使你的项目正常工作(在此处, 请访问Github的官方存储库, 详细了解NelmioCorsBundle)。
根据你的项目需求和要求, 你可能需要阅读捆绑软件的文档, 以查看需要启用和修改的选项。但是, config.yml文件中的以下配置应该可以使/ api端点(以及所有子URL [api / something, api / other-endpoint])可从其他域访问:
nelmio_cors:
defaults:
allow_credentials: false
allow_origin: []
allow_headers: []
allow_methods: []
expose_headers: []
max_age: 0
hosts: []
origin_regex: false
paths:
'^/api':
allow_origin: ['*']
allow_headers: ['*']
allow_methods: ['POST', 'PUT', 'GET', 'DELETE']
max_age: 3600
可以从任何域访问/ api端点, 并允许任何类型的标头, 你可能希望在项目中对其进行过滤。不要忘记在测试之前清除缓存, 你就可以准备就绪!
在客户端解决
如果你在尝试访问第三方API时遇到此错误(他们可能会在一段时间内无法解决), 则可以使用非传统方法来使用Javascript通过Javascript轻松从API检索数据。随处可见的免费服务。在本文中阅读有关如何使用XMLHttpRequest绕过相同原始策略的更多信息。
玩得开心 !
评论前必须登录!
注册