You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Drupal自定义内容类型MyCoolCustomType访问控制实现疑问

问题背景

我在Drupal站点里建了个叫MyCoolCustomType的自定义内容类型,想要实现:只有当用户符合指定角色,且内容自身属性(比如审核状态ModerationState)满足条件时,才允许查看这个类型的内容。

我试着通过继承RouteSubscriberBase类修改entity.node.canonical路由的requirements部分,添加_custom_access来调用专属服务,但对整个实现流程有点懵。

给出的代码:

mymodule.services.yml 配置

content_moderation.route_subscriber:
  class: Drupal\content_moderation\Routing\ContentModerationRouteSubscriber
  arguments: ['@entity_type.manager']
  tags:
    - { name: event_subscriber }

mymodule/src/EventSubscriber/SituationProAccessSubscriber.php 代码

<?php

namespace Drupal\neocadre_features\EventSubscriber;

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


class SituationProAccessSubscriber extends RouteSubscriberBase
{
  /**
   * @var AccountProxy
   */
  protected $currentUser;

  public function __construct(AccountProxyInterface $currentUser) {
    $this->currentUser = $currentUser;
  }

  protected function alterRoutes(RouteCollection $collection) {
      if ($route = $collection->get('entity.node.canonical')) {
        
        // Here it is :) maybe :)
        // Can I filter my content type here ?
      }
  }
}

我的疑问

  1. 该在哪里做MyCoolCustomType的专属过滤?是在路由订阅者里,还是在访问服务里?
  2. 这种修改路由的方式,适合给自定义内容类型做高级访问检查吗?

解答

1. 过滤逻辑的放置位置

别在路由订阅者里直接做内容类型过滤,路由订阅者的职责就是修改路由定义,具体的权限判断逻辑得放到专门的访问服务里。

正确的分工是:

  • 在路由订阅者中,给entity.node.canonical路由加上_custom_access规则,指向你的自定义访问服务。
  • 在访问服务里,先判断当前节点是不是MyCoolCustomType,再结合用户角色、内容审核状态这些条件做最终的权限校验。

2. 这种方式是否适合高级访问检查?

这种方式完全能实现复杂的访问规则,但更推荐用Drupal官方的实体权限控制方式——比如hook_node_access()钩子,或者自定义访问控制处理器(Access Control Handler),这两种方案更贴合Drupal的架构设计,后期维护也更方便。

如果你偏要用路由订阅者+自定义访问服务的方式,下面是修正后的代码示例:

第一步:修正services.yml配置

注意要指向你自己的订阅者类,不是Drupal自带的ContentModerationRouteSubscriber:

mymodule.route_subscriber:
  class: Drupal\neocadre_features\EventSubscriber\SituationProAccessSubscriber
  arguments: ['@current_user']
  tags:
    - { name: event_subscriber }
第二步:修改路由订阅者,添加自定义访问规则
<?php

namespace Drupal\neocadre_features\EventSubscriber;

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

class SituationProAccessSubscriber extends RouteSubscriberBase
{
  protected function alterRoutes(RouteCollection $collection) {
    if ($route = $collection->get('entity.node.canonical')) {
      // 指定自定义访问服务,注意服务ID要和后面注册的一致
      $route->setRequirement('_custom_access', 'mymodule.my_cool_access_checker::access');
    }
  }
}
第三步:创建自定义访问检查服务

在mymodule/src/Access/MyCoolAccessChecker.php中写入:

<?php

namespace Drupal\neocadre_features\Access;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\node\NodeInterface;
use Symfony\Component\Routing\Route;

class MyCoolAccessChecker implements AccessInterface {

  /**
   * 当前用户对象
   * @var AccountProxyInterface
   */
  protected $currentUser;

  public function __construct(AccountProxyInterface $currentUser) {
    $this->currentUser = $currentUser;
  }

  public function access(Route $route, NodeInterface $node) {
    // 先判断是不是目标内容类型
    if ($node->bundle() !== 'my_cool_custom_type') {
      // 不是的话,交给默认权限检查
      return AccessResult::neutral();
    }

    // 检查用户角色(替换成你需要的角色)
    $allowed_roles = ['editor', 'administrator'];
    if (!array_intersect($allowed_roles, $this->currentUser->getRoles())) {
      return AccessResult::forbidden()->addCacheContexts(['user.roles']);
    }

    // 检查内容审核状态(替换成你允许的状态)
    $moderation_state = $node->get('moderation_state')->value;
    if (!in_array($moderation_state, ['published', 'reviewed'])) {
      return AccessResult::forbidden()->addCacheableDependency($node);
    }

    // 所有条件都满足,允许访问
    return AccessResult::allowed()
      ->addCacheContexts(['user.roles'])
      ->addCacheableDependency($node);
  }
}
第四步:在services.yml中注册访问检查服务
mymodule.my_cool_access_checker:
  class: Drupal\neocadre_features\Access\MyCoolAccessChecker
  arguments: ['@current_user']
  tags:
    - { name: access_check, applies_to: _custom_access }

更简单的官方推荐方案:使用hook_node_access()

如果不需要修改路由,直接在模块的.module文件里实现节点访问钩子更省事:

<?php

/**
 * Implements hook_node_access().
 */
function mymodule_node_access(NodeInterface $node, $op, AccountInterface $account) {
  // 只处理视图权限和目标内容类型
  if ($op === 'view' && $node->bundle() === 'my_cool_custom_type') {
    // 检查用户角色
    if (!$account->hasAnyRole('editor', 'administrator')) {
      return AccessResult::forbidden()->addCacheContexts(['user.roles']);
    }
    // 检查内容审核状态
    $state = $node->get('moderation_state')->value;
    if (!in_array($state, ['published', 'reviewed'])) {
      return AccessResult::forbidden()->addCacheableDependency($node);
    }
    // 权限通过
    return AccessResult::allowed()
      ->addCacheContexts(['user.roles'])
      ->addCacheableDependency($node);
  }
  // 其他情况返回中立,使用默认权限逻辑
  return AccessResult::neutral();
}

这种方式不用折腾路由和服务,直接写钩子就行,完全符合Drupal的权限系统设计。


内容的提问来源于stack exchange,提问作者Ben

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 21:45:48