【问题标题】:How to avoid returning user password in Symfony 3 via JSON如何避免通过 JSON 在 Symfony 3 中返回用户密码
【发布时间】:2017-11-16 20:16:14
【问题描述】:

我正在开发一个集成了 REST API 的 Symfony 应用程序,但我遇到了一个问题,当通过 API 请求将用户实体作为 JSON 返回时,它会返回用户密码,尽管已加密,但我想避免它。

我的用户实体是:

<?php

namespace AppBundle\Entity;

use AppBundle\Util\Language;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;

/**
 * @ORM\Table(name="users")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
 */
class User implements AdvancedUserInterface, \Serializable
{
    public function __construct()
    {
        $this->isActive = true;
    }


    // Functions and parameters

    /**
     * Set password
     *
     * @param string $password
     *
     * @return User
     */
    public
    function setPassword($password)
    {
        $this->password = $password;
        return $this;
    }

    /**
     * Get password
     *
     *
     * @return string
     */
    public function getPassword()
    {
        return $this->password;
    }

    // More functions and parameters

    /** @see \Serializable::serialize() */
    public
    function serialize()
    {
        return serialize(array(
            $this->id,
            $this->username,
            $this->password,
            $this->isActive,
            $this->createdAt,
            $this->lastLogin,
        ));
    }

    /** @see \Serializable::unserialize() */
    public
    function unserialize($serialized)
    {
        list (
            $this->id,
            $this->username,
            $this->password,
            $this->isActive,
            $this->createdAt,
            $this->lastLogin,
            ) = unserialize($serialized);
    }
}

用户存储库

<?php
namespace AppBundle\Repository;

use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
use Doctrine\ORM\EntityRepository;

class UserRepository extends EntityRepository implements UserLoaderInterface
{
    public function loadUserByUsername($username)
    {
        return $this->createQueryBuilder('u')
            ->where('u.username = :username OR u.email = :email')
            ->setParameter('username', $username)
            ->setParameter('email', $username)
            ->getQuery()
            ->getOneOrNullResult();
    }
}

我有一个静态方法来构建 API 响应

public static function createSuccessfulresponse($entity, $entityName, $responseCode, $userLocale = "en", $responseMsg = "")
{
    $defResponseMsg = ($responseMsg != "" ? $responseMsg : ApiResponseCode::getMsg($responseCode, $userLocale));
    $responseArray = array();
    $responseArray['responseCode'] = $responseCode;
    $responseArray['responseMsg'] = $defResponseMsg;
    $responseArray['userLocale'] = $userLocale;
    if ($entity != null) {
        $responseArray[$entityName] = $entity;
    }
    return ApiResponseHelper::serializeResponse($responseArray);
}

响应序列化器

private static function serializeResponse($responseArray)
{
    $encoders = array(new JsonEncoder());
    $normalizers = array(new ObjectNormalizer());
    $serializer = new Serializer($normalizers, $encoders);
    return $serializer->serialize($responseArray, 'json');
}

其中一个 API 调用返回 user 实体(还有更多)

/**
 * @Route("/api/url/{uid}" )
 * @Method({"GET"})
 */
public function getByUidAction($uid)
{
    $user = $this->get('security.token_storage')->getToken()->getUser();
    $entityManager = $this->getDoctrine()->getManager();
    $entity = $entityManager->getRepository('AppBundle:Workday')->findOneBy(['uid' => $uid, 'user' => $user]);
    if($entity != null){
        return new Response(ApiResponseHelper::createSuccessfulresponse($entity, "workday", ApiResponseCode::SUCCESS_FETCH_WORKDAY, $user->getLocale()));
    }else{
        return new Response(ApiResponseHelper::createSuccessfulresponse(null, "workday", ApiResponseCode::ERROR_EXISTS_WORKDAY, $user->getLocale()));
    }
}

这是来自上述方法的一个 JSON 响应

{
    "responseCode": "successfulResponseCode",
    "responseMsg": "Data received",
    "userLocale": "es",
    "workday": {
        "id": 10,
        ... so many data
        "job": {
            "id": 11,
            .. more json data
        },
        "user": {
            "username": "amendez",
            "password": "encrypted_password",
            ... more data
        },
        ... and more data
    }
}

如您所见,我收到一个包含用户加密密码和许多其他数据的 JSON 对象,我的目标是避免返回密码键和值。

有人知道我该如何实现吗?

【问题讨论】:

  • 您是否尝试过从 User 实体的序列化函数和反序列化函数中取出密码?
  • 是的,我试过没有成功
  • OP 在 serialize 方法中需要它来保持与 FOSUserBundle 或默认防火墙实体提供程序的一致性。 symfony.com/doc/current/security/entity_provider.html
  • @fyrye 这是我没有尝试过的东西,我会这样做并告诉你,谢谢

标签: php json rest symfony


【解决方案1】:

您需要定义序列化程序组并分配所需的 getter。请参阅:https://symfony.com/doc/current/components/serializer.html#attributes-groups

首选(最佳实践)方法是分配所需的组。

use Symfony\Component\Serializer\Annotation\Groups;

class User implements AdvancedUserInterface, \Serializable
{        
    /**
     * @Groups({"api"})
     * @return string
     */
    public function getUsername()
    {
        return $this->username;
    }


    //...    

    /**
     * Get password
     * @return string
     */
    public function getPassword()
    {
        return $this->password;
    }
}

为了更容易使用 Serializer 服务而进行了编辑

在您的app/config/config.yml 中启用注释,这反过来又启用了序列化程序服务。

#config.yml
framework:
    #...
    serializer:
        enable_annotations: true

现在您可以直接调用 Serializer 服务或在自定义服务中使用 DI。

use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\HttpFoundation\JsonResponse;

private static function serializeResponse($responseArray)
{
     $serializer = $this->container->get('serializer');

     return $serializer->serialize($responseArray, JsonEncoder::FORMAT, array(
        'groups' => array('api'),
        'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS
    ));
}

手动将序列化程序组件与组一起使用。

use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;

private static function serializeResponse($responseArray)
{
    $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));

    $normalizer = new ObjectNormalizer($classMetadataFactory);
    $encoder = new JsonEncoder();
    $serializer = new Serializer(array($normalizer), array($encoder));

    return $serializer->serialize($responseArray, JsonEncoder::FORMAT, array('groups' => array('api')));
}

或者,您应该能够将其设置为被忽略属性的一部分。见:https://symfony.com/doc/current/components/serializer.html#ignoring-attributes

private static function serializeResponse($responseArray)
{
    $normalizer = new ObjectNormalizer();
    $normalizer->setIgnoredAttributes(array('password'));

    $encoder = new JsonEncoder();
    $serializer = new Serializer(array($normalizer), array($encoder));

    return $serializer->serialize($responseArray, JsonEncoder::FORMAT);
}

【讨论】:

  • 非常感谢@fyrye 我尝试了第二种方法并且它有效,所以我想第一种方法也有效,因为它是相同的,但以另一种方式。我会尝试它,因为正如您所说,这是推荐的方法,如果我发现任何标志,我会通知您。非常感谢您的帮助:)
  • 没问题。 Symfony 推荐它的原因是您对可用组上下文的声明位于中心位置。因此,维护、故障排除和验证要容易得多。与手动忽略它相反,它分散在您的控制器和服务中。在进行另一个 API 序列化调用并忘记应该忽略密码时说。
  • 是的,我明白了,我更喜欢让它更容易维护,所以我想我会把它改成另一种方法。再次感谢您的帮助和好信息
  • @AlbertoMéndez 我添加了一个更简单的方法来使用带有组注释的序列化程序。
猜你喜欢
  • 2021-07-18
  • 2019-05-10
  • 2021-11-16
  • 1970-01-01
  • 1970-01-01
  • 2013-09-24
  • 2011-09-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多