logo

Extra Block Types (EBT) - New Layout Builder experience❗

Extra Block Types (EBT) - styled, customizable block types: Slideshows, Tabs, Cards, Accordions and many others. Built-in settings for background, DOM Box, javascript plugins. Experience the future of layout building today.

Demo EBT modules Download EBT modules

❗Extra Paragraph Types (EPT) - New Paragraphs experience

Extra Paragraph Types (EPT) - analogical paragraph based set of modules.

Demo EPT modules Download EPT modules

Scroll
04/09/2025, by Ivan

JSON:API 模块旨在将通过 Drupal 的 Entity API、Field API 和 Typed Data API 定义的数据模型,按符合 JSON:API 规范 的方式通过 API 暴露出来,以便与由 Drupal 管理的数据(实体)进行交互。

在此过程中,它会遵循 Drupal 针对这些数据的所有安全措施:

  • 遵循实体访问(Entity Access)。
  • 遵循字段访问(Field Access)。
  • 在修改数据时,遵循验证约束(validation constraints)。
  • 遵循 internal 标志(参见关于如何在实体类型定义、字段定义或属性定义上设置它的文档)。

换言之:JSON:API 不会绕过任何既有的安全措施,也不会额外添加自己的安全层;它复用的是 Drupal 的基础能力。

实体类型、字段类型与数据类型中的缺陷可能导致安全漏洞

尽管如此,实现实体类型、字段类型和数据类型以及其访问控制处理器与验证约束的代码中,确实可能存在缺陷。这在很大程度上可归因于 Drupal 的历史包袱:Drupal 最初没有验证约束(validation constraints),而是表单验证回调;在 Drupal 核心中向“先 API”思维的转变或可视为已完成,但对贡献模块(contrib)或自定义模块则无法保证。

这些缺陷可能导致安全漏洞;过去也确实发生过。此类漏洞并不仅限于 JSON:API 模块;它们同样会影响例如 RESTful Web Services 模块,以及任何与 Entity API 交互的 PHP 代码。

然而,由于恶意用户比起访问 PHP API,更容易访问像 JSON:API 或 RESTful Web Services 这样的 HTTP API,因此在这种情况下需要格外小心。与其他 HTTP API 模块不同,JSON:API 默认就具有更大的 API 表面:为了尽可能提升开发者体验,所有非 internal 的实体类型都会默认可用(当然仍会遵循实体访问),这就需要更多安全考量。

六点安全考量

1. 使用稳定贡献模块的重要性

由实体类型、字段类型与数据类型引发的安全漏洞,仅会对发布在 Drupal.org 上、受 安全公告政策覆盖的稳定模块尽快修复。自定义模块与非稳定的贡献模块不在覆盖范围内。 如果你在使用这些模块,请务必格外谨慎。

2. 审计实体与字段访问

无论你是否使用 JSON:API 或任何其他类似 API 的模块,都强烈建议在 Drupal 站点上审计实体访问与字段访问。如果启用了 JSON:API 的写入能力,这一点尤为重要。

3. 仅暴露你需要的内容

当特定资源类型(实体类型 + 包(bundle))不需要被暴露时,在确保对其访问被拒绝之后,你还可以更进一步将其禁用。要禁用某个资源类型或字段,可以在自定义模块中实现一个PHP API,或者使用提供禁用资源类型与字段 UI 的 JSON:API Extras 贡献模块。这并非总是可行,但在站点所有者同时也拥有所有 API 客户端的情况下,你可以这样做以尽可能缩小 API 表面。

4. 只读模式

如果你的场景只需要读取数据,你可以在 /admin/config/services/jsonapi 启用 JSON:API 的只读模式。这能够减轻来自既有验证约束与写入逻辑中假设性、尚未被发现的缺陷所带来的风险。由于大多数现代解耦式 Drupal 架构只需要读取数据,只读模式在默认情况下是开启的。(适用于 Drupal 核心的 JSON:API,以及贡献模块 2.4 及更高版本。)

5. 以隐蔽性实现安全:设置秘密基础路径

JSON:API 的基础路径默认为 /jsonapi。你可以将其修改为类似 /hidden/b69dhj027ooae/jsonapi 的路径,以降低自动化攻击的有效性。若尚不存在,请创建 sites/example.com/services.yml 并加入如下内容:

parameters:
  jsonapi.base_path: /hidden/b69dhj027ooae/jsonapi

6. 通过移除部分路由,限制可创建或可编辑的实体 bundle

如果你只需要通过 JSON:API 创建或更新部分实体 bundle,你可以在自定义模块中实现一个事件订阅器(event subscriber),只保留白名单中的 POST 与 PATCH 路由。此方式在关闭只读模式后生效,并可能需要重建路由(router rebuild)。

在模块的 services.yml 中添加一个服务:

services:
  mymodule.route_subscriber:
    class: Drupal\mymodule\Routing\JsonapiLimitingRouteSubscriber
    tags:
      - { name: event_subscriber }

创建事件订阅器。下面的示例还会使得无法通过 JSON:API 删除任何内容:

<?php

namespace Drupal\mymodule\Routing;

use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

/**
 * Class JsonapiLimitingRouteSubscriber.
 *
 * Remove all DELETE routes from jsonapi resources to protect content.
 *
 * Remove POST and PATCH routes from jsonapi resources except for those
 * we want end users to create and update via the decoupled API.
 */
class JsonapiLimitingRouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  protected function alterRoutes(RouteCollection $collection) {
    $mutable_types = $this->mutableResourceTypes();
    foreach ($collection as $name => $route) {
      $defaults = $route->getDefaults();
      if (!empty($defaults['_is_jsonapi']) && !empty($defaults['resource_type'])) {
        $methods = $route->getMethods();
        if (in_array('DELETE', $methods)) {
          // We never want to delete data, only unpublish.
          $collection->remove($name);
        }
        else {
          $resource_type = $defaults['resource_type'];
          if (empty($mutable_types[$resource_type])) {
            if (in_array('POST', $methods) || in_array('PATCH', $methods)) {
              $collection->remove($name);
            }
          }
        }
      }
    }
  }

  /**
   * Get mutable resource types, exposed to user changes via API.
   *
   * @return array
   *   List of mutable jsonapi resource types as keys.
   */
  public function mutableResourceTypes(): array {
    return [
      'node--article' => TRUE,
      'node--document' => TRUE,
      'custom_entity--custom_entity' => TRUE,
    ];
  }

}

通过额外权限限制对所有 JSON:API 路由的访问

当将 JSON:API 用于后端集成、受限的 API 客户端或其他非公开用例时,可能希望将所有 JSON:API 路由限制为仅特定权限的用户可访问。你可以在上述路由订阅器中添加如下代码片段来实现(或者作为补充措施):

    // Limit access to all jsonapi routes with an extra permission.
    foreach ($collection as $route) {
      $defaults = $route->getDefaults();
      if (!empty($defaults['_is_jsonapi'])) {
        $route->setRequirement('_permission', 'FOO custom access jsonapi');
      }
    }

随后在 FOO.permissions.yml 中定义该权限,并将其授予所需的用户角色。

文章来自 Drupal 文档