Symfony 的安全性

API Platform安全层建立在 Symfony 安全组件之上。支持其所有功能,包括全局访问控制指令 。API Platform 还提供了方便的访问控制表达式 ,您可以在资源和操作层面应用这些表达式。

<?php
// api/src/Entity/Book.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Secured resource.
 */
#[ApiResource(security: "is_granted('ROLE_USER')")]
#[Get]
#[Put(security: "is_granted('ROLE_ADMIN') or object.owner == user")]
#[GetCollection]
#[Post(security: "is_granted('ROLE_ADMIN')")]
#[ORM\Entity]
class Book
{
    #[ORM\Id, ORM\Column, ORM\GeneratedValue]
    private ?int $id = null;

    #[ORM\Column]
    #[Assert\NotBlank]
    public string $title;

    #[ORM\ManyToOne]
    public User $owner;

    // ...
}

资源签名也可以在属性级别修改:

<?php
// api/src/Entity/Book.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiProperty;

class Book
{
    //...

    /**
     * @var string Property viewable and writable only by users with ROLE_ADMIN
     */
    #[ApiProperty(security: "is_granted('ROLE_ADMIN')", securityPostDenormalize: "is_granted('UPDATE', object)")]
    private $adminOnlyProperty;
}

在此示例中:

  • 用户必须登录才能与 Book 资源(在资源级别配置)进行交互
  • 只有具有角色 ROLE_ADMIN 的用户才能创建新资源(在发布作中配置)
  • 只有拥有 ROLE_ADMIN 或拥有当前对象的用户才能替换现有帐簿(在放置作中配置)
  • 只有拥有 ROLE_ADMIN 的用户才能查看或修改 adminOnlyProperty 属性。只有具有 ROLE_ADMIN 的用户才能创建指定 adminOnlyProperty 值的新资源。
  • 只有在账簿上被授予 UPDATE 属性的用户(通过选民)才能写入字段

可用的变量包括:

  • user:当前登录的对象(如果有)
  • object:非规范化期间的当前资源类、规范化期间的当前资源或收集资源的集合
  • previous_object:(仅限 securityPostDenormalize)在进行修改之前的对象克隆 – 对于创建作,这为 null
  • 请求 (仅在资源级别):当前请求

安全属性中的访问控制检查始终在非规范化步骤之前执行。这意味着对于 PUT 或 PATCH 请求, 对象不包含用户提交的值,而是当前存储在持久层中的值。

非规范化后执行访问控制规则

在某些情况下,在非规范化步骤之后执行安全性可能很有用。为此,请使用 securityPostDenormalize 属性:

<?php
// api/src/Entity/Book.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Put;

#[ApiResource]
#[Get]
#[Put(securityPostDenormalize: "is_granted('ROLE_ADMIN') or (object.owner == user and previous_object.owner == user)")]
class Book
{
    // ...
}

这一次, 对象变量包含在非规范化过程中从 HTTP 请求正文中提取的数据。但是,该对象尚未持久化。

此外,在某些情况下,您需要对原始数据进行安全检查。例如,在这里,只有实际所有者才能编辑他们的书。在这些情况下,可以使用包含从状态提供程序读取的对象的 previous_object 变量。

previous_object 变量中的值是从原始对象克隆的。请注意,默认情况下,此克隆不是深层克隆(它不克隆关系,关系是引用)。若要进行深度克隆,请在相关资源类中实现 clone 方法 。

使用选民挂钩自定义权限检查

挂钩自定义访问控制逻辑的最简单和推荐的方法是编写 Symfony Voter 类 。您的自定义投票器将通过 is_granted() 函数自动用于安全表达式。

为了将当前对象提供给您的选民,请使用表达式 is_granted('READ', object)

例如:

<?php
// api/src/Entity/Book.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;

#[ApiResource(security: "is_granted('ROLE_USER')")]
#[Get(security: "is_granted('BOOK_READ', object)")]
#[Put(security: "is_granted('BOOK_EDIT', object)")]
#[Delete(security: "is_granted('BOOK_DELETE', object)")]
#[GetCollection]
#[Post(securityPostDenormalize: "is_granted('BOOK_CREATE', object)")]
class Book
{
    // ...
}

请注意,如果您同时使用安全性:“...”,然后 "post" => ["securityPostDenormalize" => "..."] 调用顶层安全性 ,然后调用 securityPostDenormalize。这可能会导致不良行为,因此请避免同时使用它们。如果需要使用 securityPostDenormalize,请考虑为其他作而不是全局作添加安全性 

使用 bin/console make:voter 命令创建一个 BookVoter

<?php
// api/src/Security/Voter/BookVoter.php

namespace App\Security\Voter;

use App\Entity\Book;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;

class BookVoter extends Voter
{
    private $security = null;

    public function __construct(Security $security)
    {
        $this->security = $security;
    }

    protected function supports($attribute, $subject): bool
    {
        $supportsAttribute = in_array($attribute, ['BOOK_CREATE', 'BOOK_READ', 'BOOK_EDIT', 'BOOK_DELETE']);
        $supportsSubject = $subject instanceof Book;

        return $supportsAttribute && $supportsSubject;
    }

    /**
     * @param string $attribute
     * @param Book $subject
     * @param TokenInterface $token
     * @return bool
     */
    protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
    {
        /** ... check if the user is anonymous ... **/

        switch ($attribute) {
            case 'BOOK_CREATE':
                if ( $this->security->isGranted(Role::ADMIN) ) { return true; }  // only admins can create books
                break;
            case 'BOOK_READ':
                /** ... other authorization rules ... **/
        }

        return false;
    }
}

注意 1:在 POST 方法上使用 Voters 时:投票者需要一个 $attribute 和 $subject 作为输入参数,因此您必须使用 securityPostDenormalize(即 "post" = { "securityPostDenormalize" = "is_granted('BOOK_CREATE', object)" } ),因为该对象在非规范化之前不存在(尚未创建)。

注 2:不能在集合 GET 方法上使用 Voters,请改用集合筛选器 。

# 配置访问控制错误消息

默认情况下,当 API 请求被拒绝时,您将收到“访问被拒绝”消息。您可以通过配置 securityMessage 属性或属性 securityPostDenormalizeMessage 来更改它。

例如:

<?php
// api/src/Entity/Book.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;

#[ApiResource(
    security: "is_granted('ROLE_USER')",
    operations: [
        new Get(
            security: "is_granted('ROLE_USER') and object.owner == user",
            securityMessage: 'Sorry, but you are not the book owner.'
        )
        new Put(
            securityPostDenormalize: "is_granted('ROLE_ADMIN') or (object.owner == user and previous_object.owner == user)",
            securityPostDenormalizeMessage: 'Sorry, but you are not the actual book owner.'
        )
        new Post(
            security: "is_granted('ROLE_ADMIN')",
            securityMessage: 'Only admins can add books.'
        )
    ]
)]
class Book
{
    // ...
}

根据当前用户权限筛选集合

必须根据当前用户的角色或权限筛选集合,必须直接在状态提供程序级别完成。例如,当使用 Doctrine ORM、MongoDB 和 ElasticSearch 的内置适配器时,应该使用扩展从集合中删除条目。扩展允许自定义生成的 DQL/Mongo/Elastic/…查询,用于检索集合(例如,根据当前连接的用户添加 WHERE 子句),而不是使用访问控制表达式。由于扩展是服务,您可以将 Symfony 安全类注入其中以访问当前用户的角色和权限。

如果使用自定义状态提供程序 ,则必须根据所依赖的持久性层实现筛选逻辑。

禁用作

要完全禁用应用程序中的某些作,请参阅禁用作部分。

根据当前用户更改序列化组

了解如何根据当前登录的用户动态更改当前序列化程序上下文。

Next

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注