如何在创建时自动将当前授权的用户添加到资源(POST
。]
我正在使用JWT身份验证,并且/ api /路由受到保护,防止未经授权的用户使用。我想进行设置,以便在通过身份验证的用户创建新资源时(即,通过向POST
发送/api/articles
请求),新创建的Article
资源与经过身份验证的用户有关。
我目前正在使用每种资源类型的自定义EventSubscriber
从令牌存储中添加用户。
这是订户基本类的要点:https://gist.github.com/dsuurlant/5988f90e757b41454ce52050fd502273
以及扩展它的实体订户:https://gist.github.com/dsuurlant/a8af7e6922679f45b818ec4ddad36286
但是,例如,如果实体构造函数要求用户作为参数,则此方法不起作用。
例如
class Book {
public User $owner;
public string $name;
public class __construct(User $user, string $name) {
$this->owner = $user;
$this->name = $name;
}
}
如何在创建实体时自动注入授权用户?
暂时,我正在使用DTOs and data transformers。
主要缺点是必须为每个需要这种行为的资源创建一个新的DTO。
作为一个简单的例子,我正在做这样的事情:
class BootDtoTransformer implements DataTransformerInterface
{
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function transform($data, string $to, array $context = [])
{
$owner = $this->security->getUser();
return $new Book($owner, $data->name);;
}
public function supportsTransformation($data, string $to, array $context = []): bool
{
if ($data instanceof Book) {
return false;
}
return Book::class === $to && null !== ($context['input']['class'] ?? null);
}
}
这在逻辑上仅适用于单个资源。为了拥有用于多种资源的通用转换器,我最终使用一些接口来划分“拥有的”资源,并进行一些实例化以实例化每个类。
我本以为在非规范化阶段这是可行的,但我无法使它正常工作。
我的看法:没有Gedmo,没有魔法。正确的OOP代码。虽然这本身并不能回答您的问题,但确实可以解决您的问题。
[使用Factory Pattern通过工厂抽象对象的创建。
<?php
declare(strict_types=1);
namespace App\Books\Domain\Factory;
use App\Books\Domain\Model\Book;
interface BookFactory
{
public function create(string $name): Book;
}
随后是实现:
<?php
declare(strict_types=1);
namespace App\Books\Domain\Factory;
use App\Books\Domain\Model\Book;
use Symfony\Component\Security\Core\Security;
final class SymfonySecurityBookFactory implements BookFactory
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function create(string $name): Book
{
return new Book($this->security->getUser(), $name);
}
}
示例单元测试:
final class SymfonySecurityBookFactoryTest extends TestCase
{
public function testItCreatesABook(): void
{
$user = new DomainUser();
$security = $this->prophesize(Security::class);
$security->getUser()->willReturn($user);
$factory = new SymfonySecurityBookFactory($security->reveal());
$book = $factory->create('Test');
$this->assertSame($user, $book->getUser());
$this->assertSame('Test', $book->getName());
}
}
示例用法形式:
public function buildForm(FormBuilderInterface $builder): void
{
$builder->setData(function (FormInterface $form): Book {
return $this->bookFactory->create($form->get('name')->getData());
});
// … individual fields
}
正如@ nealio82和@lavb所说,您应该查看Gedmo \ Blameable,它可以帮助您将属性存储为createdBy
或updatedBy
,您可以在其中存储创建资源的User
。
BlameableStofDoctrineExtensionsBundle
然后处理访问,请看一下Voter,它对处理安全性和不同的访问非常有用。
Official Symfony documentation about Voters
例如
图书实体
...
use Gedmo\Mapping\Annotation as Gedmo;
class Book {
...
/**
* @var string $createdBy
*
* @Gedmo\Blameable(on="create")
* @ORM\Column
*/
public User $owner;
public function getOwner() {
return $this->owner;
}
public function setOwner(User $owner) {
$this->owner = $owner
}
}
src /安全性/投票/ BookVoter
namespace App\Security;
use App\Entity\Book;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class BookVoter extends Voter
{
const VIEW = 'view';
const EDIT = 'edit';
protected function supports(string $attribute, $subject)
{
// if the attribute isn't one we support, return false
if (!in_array($attribute, [self::VIEW, self::EDIT])) {
return false;
}
// only vote on `Book` objects
if (!$subject instanceof Book) {
return false;
}
return true;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token) {
$user = $token->getUser();
if (!$user instanceof User) {
// the user must be logged in; if not, deny access
return false;
}
/** @var Book $book */
$book = $subject;
switch ($attribute) {
case self::VIEW:
return $this->canView($book, $user);
case self::EDIT:
return $this->canEdit($book, $user);
}
throw new \LogicException('This code should not be reached!');
}
private function canEdit(Book $book, User $user) {
// ONLY OWNER CAN EDIT BOOK
return $user === $book->getOwner();
}
private function canView(Book $book, User $user) {
// DIFFERENT LOGIC ?
return $user === $book->getOwner();
}
...
}