个性化阅读
专注于IT技术分析

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

点击下载

本文概述

当有人使用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绕过相同原始策略的更多信息。

玩得开心 !

赞(0)
未经允许不得转载:srcmini » 如何使用自己的Symfony 3 API解决客户端”Access-Control-Allow-Origin”请求错误

评论 抢沙发

评论前必须登录!