如何使用自己的Symfony 3 API解决客户端”Access-Control-Allow-Origin”请求错误

本文概述

  • 解决控制器中的响应
  • 解决静态文件和已实现的API
  • 在客户端解决
当有人使用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” 。
【如何使用自己的Symfony 3 API解决客户端” Access-Control-Allow-Origin” 请求错误】通常, 在PHP中, 你可以通过实现以下标头在脚本中启用CORS:
< ?phpheader("Access-Control-Allow-Origin: *");

*表示允许所有域访问服务器中脚本的响应。你只能将1个域设置为值, 否则, 以后你会遇到更多麻烦, 此外, 如果你需要添加对多个域的支持, 请在Stack Overflow上检查此问题。
但是, 当你使用symfony时, 就不会这样做。相反, 你需要在控制器中修改返回的响应。
解决控制器中的响应在本示例中, 使用控制器模型, 我们将使用一个简单的控制器, 该控制器生成错误(没有标题)并返回一个简单的JSON响应:
< ?phpnamespace 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标头:
< ?phppublic 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:
< ?phpnamespace 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)中注册捆绑软件:
< ?phppublic function registerBundles(){$bundles = [///..///new Nelmio\CorsBundle\NelmioCorsBundle(), ///..///]; ///..///}

最后, 继续进行所需的配置, 以使你的项目正常工作(在此处, 请访问Github的官方存储库, 详细了解NelmioCorsBundle)。
根据你的项目需求和要求, 你可能需要阅读捆绑软件的文档, 以查看需要启用和修改的选项。但是, config.yml文件中的以下配置应该可以使/ api端点(以及所有子URL [api / something, api / other-endpoint])可从其他域访问:
nelmio_cors:defaults:allow_credentials: falseallow_origin: []allow_headers: []allow_methods: []expose_headers: []max_age: 0hosts: []origin_regex: falsepaths:'^/api':allow_origin: ['*']allow_headers: ['*']allow_methods: ['POST', 'PUT', 'GET', 'DELETE']max_age: 3600

可以从任何域访问/ api端点, 并允许任何类型的标头, 你可能希望在项目中对其进行过滤。不要忘记在测试之前清除缓存, 你就可以准备就绪!
在客户端解决如果你在尝试访问第三方API时遇到此错误(他们可能会在一段时间内无法解决), 则可以使用非传统方法来使用Javascript通过Javascript轻松从API检索数据。随处可见的免费服务。在本文中阅读有关如何使用XMLHttpRequest绕过相同原始策略的更多信息。
玩得开心 !

    推荐阅读