API 关系中的平台 GraphQL 安全性
API Platform GraphQL Security in Relationships
我的问题是通过 GraphQL 使用时实体的安全实现,查询是完美的,它们 return 必要的数据,但是当生成查询和多对一时会出现问题关系仅限于具有角色“ROLE_ADMIN”,即使用户具有角色“IS_AUTHENTICATED_ANONYMOUSLY”
,查询 return 也是数据
如何添加安全层使得受保护关系的数据无法获取?
The product query must be seen without any role.
附加信息
GraphQL 查询用户管理员
query {
userAdmin(id: "api/user_admins/1") {
id
name
}
}
GraphQL 查询用户管理结果正常
{
"errors": [
{
"message": "Sorry, but you don't have access",
"extensions": {
"category": "graphql"
}
]
}
GraphQL 查询产品
query {
products {
edges {
node {
name
price
user {
name
}
}
}
}
}
GraphQL 查询产品结果失败
{
"data": {
"products": {
"edges": [
{
"node": {
"name": "GERLACH-HAAG",
"price": "175",
"user": {
"name": "Sidney Deane" /** this information should not be seen **/
}
}
}
]
}
}
}
实体产品配置
<?php
/**
* @ApiResource(
* graphql={
* "item_query",
* "collection_query",
* "delete"={ "security" = "is_granted('ROLE_ADMIN')" },
* "create"={ "security" = "is_granted('ROLE_ADMIN')" },
* "update"={ "security" = "is_granted('ROLE_ADMIN')" }
* }
* )
* @ORM\Table(name="TBL_PRODUCTS")
* @ORM\Entity(repositoryClass=ProductRepository::class)
* @ORM\HasLifecycleCallbacks()
*/
class Product
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="bigint", name="ID")
*/
private $id;
/**
* @ORM\Column(type="string", length=180, name="NAME")
*/
private $name;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\UserAdmin")
* @ORM\JoinColumn(name="USER", referencedColumnName="ID")
*/
private $user;
实体用户管理员配置
<?php
/**
* @ApiResource(
* graphql={
* "item_query"={ "security" = "is_granted('ROLE_ADMIN')" },
* "collection_query"={ "security" = "is_granted('ROLE_ADMIN')" },
* "delete"={ "security" = "is_granted('ROLE_ADMIN')" },
* "create"={ "security" = "is_granted('ROLE_ADMIN')" },
* "update"={ "security" = "is_granted('ROLE_ADMIN')" }
* }
* )
* @ORM\Table(name="TBL_USERS_ADMIN")
* @ORM\Entity(repositoryClass=UserAdminRepository::class)
* @ORM\HasLifecycleCallbacks()
*/
class UserAdmin implements UserInterface
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="bigint", name="ID")
*/
private $id;
/**
* @ORM\Column(type="string", length=180, name="USERNAME")
*/
private $username;
/**
* @ORM\Column(type="string", length=180, name="PASSWORD")
*/
private $password;
/**
* @ORM\Column(type="string", length=180, name="NAME")
*/
private $name;
请帮忙!!!!
尝试对您的实体使用排除策略。您将需要使用 JMS Serializer Bundle
示例:
use JMS\Serializer\Annotation as Serializer;
/**
* @ORM\Table(name="TBL_PRODUCTS")
* @ORM\Entity(repositoryClass=ProductRepository::class)
* @ORM\HasLifecycleCallbacks()
* @Serializer\ExclusionPolicy("all")
*/
class Product
{
/**
* @ORM\ManyToOne(targetEntity="App\Entity\UserAdmin")
* @ORM\JoinColumn(name="USER", referencedColumnName="ID")
* @Serializer\Groups({"ROLE_ADMIN"}) // only users with ROLE_ADMIN will see this
* @Serializer\Expose()
*/
private $user;
/**
* ...
* @Serializer\Groups({"ROLE_SUPER_ADMIN"}) // only users with ROLE_SUPER_ADMIN will see this
* @Serializer\Expose()
*/
private $superSecretData;
...
}
仅当用户拥有 ROLE_ADMIN 时,才会公开 $user
属性。
在公开数据的 controller/service 中,您应该将用户角色添加到序列化上下文中:
use JMS\Serializer\SerializerInterface;
...
$entities = $repository->findAll();
$responseData = $this->serializer->serialize(
$entities,
'json',
SerializationContext::create()
->setGroups(['ROLE_USER']) // Add all roles you want
);
$response = new Response(
$responseData,
Response::HTTP_OK
);
$response->headers->set('Content-Type', 'application/json');
return $response;
这样您应该能够根据用户的角色排除或公开一些属性。
security
属性允许定义执行给定查询所需的角色或权限。但是,它没有定义用户可以通过此查询访问哪些可用关系。为此,您可以使用 dynamic serialization groups.
基本上,用特定的序列化组标记需要在响应中返回特殊角色的属性,例如@Groups("admin")
,然后创建一个动态序列化上下文构建器来添加特殊组,如果连接的用户有这个特殊的角色:
<?php
namespace App\Serializer;
use ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilderInterface;
use App\Entity\Book;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
final class BookContextBuilder implements SerializerContextBuilderInterface
{
private $decorated;
private $authorizationChecker;
public function __construct(SerializerContextBuilderInterface $decorated, AuthorizationCheckerInterface $authorizationChecker)
{
$this->decorated = $decorated;
$this->authorizationChecker = $authorizationChecker;
}
public function create(?string $resourceClass, string $operationName, array $resolverContext, bool $normalization): array
{
$context = $this->decorated->create($resourceClass, $operationName, $resolverContext, $normalization);
$resourceClass = $context['resource_class'] ?? null;
if ($resourceClass === Book::class && isset($context['groups']) && $this->authorizationChecker->isGranted('ROLE_ADMIN') && false === $normalization) {
$context['groups'][] = 'admin';
}
return $context;
}
}
在主版本(API 平台 2.6)中,the security
attribute is now also available for properties。不幸的是,此功能目前仅在 REST 子系统中可用,但我们将非常高兴合并一个 Pull Request,将对此属性的支持添加到 GraphQL 子系统中。
我的问题是通过 GraphQL 使用时实体的安全实现,查询是完美的,它们 return 必要的数据,但是当生成查询和多对一时会出现问题关系仅限于具有角色“ROLE_ADMIN”,即使用户具有角色“IS_AUTHENTICATED_ANONYMOUSLY”
,查询 return 也是数据如何添加安全层使得受保护关系的数据无法获取?
The product query must be seen without any role.
附加信息
GraphQL 查询用户管理员
query {
userAdmin(id: "api/user_admins/1") {
id
name
}
}
GraphQL 查询用户管理结果正常
{
"errors": [
{
"message": "Sorry, but you don't have access",
"extensions": {
"category": "graphql"
}
]
}
GraphQL 查询产品
query {
products {
edges {
node {
name
price
user {
name
}
}
}
}
}
GraphQL 查询产品结果失败
{
"data": {
"products": {
"edges": [
{
"node": {
"name": "GERLACH-HAAG",
"price": "175",
"user": {
"name": "Sidney Deane" /** this information should not be seen **/
}
}
}
]
}
}
}
实体产品配置
<?php
/**
* @ApiResource(
* graphql={
* "item_query",
* "collection_query",
* "delete"={ "security" = "is_granted('ROLE_ADMIN')" },
* "create"={ "security" = "is_granted('ROLE_ADMIN')" },
* "update"={ "security" = "is_granted('ROLE_ADMIN')" }
* }
* )
* @ORM\Table(name="TBL_PRODUCTS")
* @ORM\Entity(repositoryClass=ProductRepository::class)
* @ORM\HasLifecycleCallbacks()
*/
class Product
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="bigint", name="ID")
*/
private $id;
/**
* @ORM\Column(type="string", length=180, name="NAME")
*/
private $name;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\UserAdmin")
* @ORM\JoinColumn(name="USER", referencedColumnName="ID")
*/
private $user;
实体用户管理员配置
<?php
/**
* @ApiResource(
* graphql={
* "item_query"={ "security" = "is_granted('ROLE_ADMIN')" },
* "collection_query"={ "security" = "is_granted('ROLE_ADMIN')" },
* "delete"={ "security" = "is_granted('ROLE_ADMIN')" },
* "create"={ "security" = "is_granted('ROLE_ADMIN')" },
* "update"={ "security" = "is_granted('ROLE_ADMIN')" }
* }
* )
* @ORM\Table(name="TBL_USERS_ADMIN")
* @ORM\Entity(repositoryClass=UserAdminRepository::class)
* @ORM\HasLifecycleCallbacks()
*/
class UserAdmin implements UserInterface
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="bigint", name="ID")
*/
private $id;
/**
* @ORM\Column(type="string", length=180, name="USERNAME")
*/
private $username;
/**
* @ORM\Column(type="string", length=180, name="PASSWORD")
*/
private $password;
/**
* @ORM\Column(type="string", length=180, name="NAME")
*/
private $name;
请帮忙!!!!
尝试对您的实体使用排除策略。您将需要使用 JMS Serializer Bundle
示例:
use JMS\Serializer\Annotation as Serializer;
/**
* @ORM\Table(name="TBL_PRODUCTS")
* @ORM\Entity(repositoryClass=ProductRepository::class)
* @ORM\HasLifecycleCallbacks()
* @Serializer\ExclusionPolicy("all")
*/
class Product
{
/**
* @ORM\ManyToOne(targetEntity="App\Entity\UserAdmin")
* @ORM\JoinColumn(name="USER", referencedColumnName="ID")
* @Serializer\Groups({"ROLE_ADMIN"}) // only users with ROLE_ADMIN will see this
* @Serializer\Expose()
*/
private $user;
/**
* ...
* @Serializer\Groups({"ROLE_SUPER_ADMIN"}) // only users with ROLE_SUPER_ADMIN will see this
* @Serializer\Expose()
*/
private $superSecretData;
...
}
仅当用户拥有 ROLE_ADMIN 时,才会公开 $user
属性。
在公开数据的 controller/service 中,您应该将用户角色添加到序列化上下文中:
use JMS\Serializer\SerializerInterface;
...
$entities = $repository->findAll();
$responseData = $this->serializer->serialize(
$entities,
'json',
SerializationContext::create()
->setGroups(['ROLE_USER']) // Add all roles you want
);
$response = new Response(
$responseData,
Response::HTTP_OK
);
$response->headers->set('Content-Type', 'application/json');
return $response;
这样您应该能够根据用户的角色排除或公开一些属性。
security
属性允许定义执行给定查询所需的角色或权限。但是,它没有定义用户可以通过此查询访问哪些可用关系。为此,您可以使用 dynamic serialization groups.
基本上,用特定的序列化组标记需要在响应中返回特殊角色的属性,例如@Groups("admin")
,然后创建一个动态序列化上下文构建器来添加特殊组,如果连接的用户有这个特殊的角色:
<?php
namespace App\Serializer;
use ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilderInterface;
use App\Entity\Book;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
final class BookContextBuilder implements SerializerContextBuilderInterface
{
private $decorated;
private $authorizationChecker;
public function __construct(SerializerContextBuilderInterface $decorated, AuthorizationCheckerInterface $authorizationChecker)
{
$this->decorated = $decorated;
$this->authorizationChecker = $authorizationChecker;
}
public function create(?string $resourceClass, string $operationName, array $resolverContext, bool $normalization): array
{
$context = $this->decorated->create($resourceClass, $operationName, $resolverContext, $normalization);
$resourceClass = $context['resource_class'] ?? null;
if ($resourceClass === Book::class && isset($context['groups']) && $this->authorizationChecker->isGranted('ROLE_ADMIN') && false === $normalization) {
$context['groups'][] = 'admin';
}
return $context;
}
}
在主版本(API 平台 2.6)中,the security
attribute is now also available for properties。不幸的是,此功能目前仅在 REST 子系统中可用,但我们将非常高兴合并一个 Pull Request,将对此属性的支持添加到 GraphQL 子系统中。