Авторизація для API за допомогою токенів

Як і обіцяв раніше, продовжую свою серію статей про створення API на Symfony2. Сьогодні я б хотів розповісти про авторизації. З популярних бандлів є JWTAuthenticationBundle і FOSOAuthServerBundle, у кожного є свої плюси і мінуси, але мені хотілося б розповісти як зробити авторизацію самому, щоб зрозуміти як це працює.
Для початку створимо сутність UserAccessToken, в якій будемо зберігати токени доступу користувачів.
<?php

namespace App\CommonBundle\Entity;

use Doctrine\ORM\Mapping AS ORM;

/**
* @ORM\Entity
* @ORM\Table(name="user_access_tokens")
*/
class UserAccessToken
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="АВТО")
* @ORM\Column(name="id", type="integer")
*/
protected $id;

/**
* @ORM\ManyToOne(targetEntity="User")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="SET NULL")
*/
protected $user;

/**
* Тут ми будемо зберігати наш токен. Токен необхідно генерувати самому і як можна складніше і довше, щоб виключити можливість підбору
* 
* @ORM\Column(name="access_token", type="string")
*/
protected $accessToken;

/**
* Дата, після якої токен буде вважатися не активним
* 
* @ORM\Column(name="expired_at", type="datetime")
*/
protected $expiredAt;

/**
* @ORM\Column(name="created_at", type="datetime")
*/
protected $createdAt;

}


Тепер створимо Listener, який буде слухати всі запити до вашого API і авторизувати користувача.

<?php

namespace App\CommonBundle\Listener;

use App\CommonBundle as Common;
use Doctrine\ORM\EntityManager;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;

class AccessTokenListener
{
private $entityManager;
private $securityContext;
private $exclude = [
'/users/login', '/users/registration',
];

const EMPTY_ACCESS_TOKEN = 'empty_access_token';
const INVALID_ACCESS_TOKEN = 'invalid_access_token';
const ACCESS_TOKEN_EXPIRED = 'access_token_expired';

public function __construct(EntityManager $entityManager, SecurityContextInterface $securityContext)
{
$this->entityManager = $entityManager;
$this->securityContext = $securityContext;
}

/**
* @return Common\Entity\UserAccessToken
*/
private function getByAccessToken($accessToken)
{
return $this->entityManager->getRepository('CommonBundle:UserAccessToken')->findOneByAccessToken($accessToken);
}

public function beforeController(GetResponseEvent $event)
{
// Робимо доступними без сертифіката необхідні для нас URL
// Це необхідно для того, щоб відкрити метод для авторизації і тд
if (in_array($event->getRequest()->getPathInfo(), $this->exclude)) {
return;
}

// Дивимося заголовок з назвою X-Access Token, якщо він порожній – повертаємо помилку
$accessToken = $event->getRequest()->headers->get('X-Access Token');
if (!$accessToken) {
$event->setResponse(new JsonResponse(['error' => self::EMPTY_ACCESS_TOKEN], 403));
return;
}

// Шукаємо в базі користувача з токена
$token = $this->getByAccessToken($accessToken);
if (!$token) {
$event->setResponse(new JsonResponse(['error' => self::INVALID_ACCESS_TOKEN], 403));
return;
}

// Перевіряємо чи актуальне все ще токен
if ($token->getExpiredAt() <= new \DateTime('now')) {
$event->setResponse(new JsonResponse(['error' => self::ACCESS_TOKEN_EXPIRED], 403));
return;
}

// Встановлюємо користувача, щоб він був доступний в контролерах через $this->getUser()
$user = $token->getUser();

$usernamePasswordToken = new UsernamePasswordToken($user, $user->getPassword(), "main", $user->getRoles());
$this->securityContext->setToken($usernamePasswordToken);
}
}


І підключимо його в services.yml
common.listener.access_token:
class: App\CommonBundle\Listener\AccessTokenListener
arguments: [@doctrine.orm.entity_manager, @security.context]
tags:
- { name: kernel.event_listener, event: kernel.request, method: beforeController }


На цьому практично все, тепер тільки залишається зробити простенький метод для авторизації, який буде приймати логін і пароль і в разі успіху створювати новий UserAccessToken зі згенерованим значенням accessToken і повертати його у відповіді.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.