Авторизация для API с помощью токенов
Как и обещал ранее, продолжаю свою серию статей про создание API на Symfony2. Сегодня я бы хотел рассказать о авторизации. Из популярных бандлов есть JWTAuthenticationBundle и FOSOAuthServerBundle, у каждого есть свои плюсы и минусы, но мне хотелось бы рассказать как сделать авторизацию самому, чтобы понимать как это работает.Для начала, создадим сущность UserAccessToken, в которой будем хранить токены доступа пользователей.
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=«AUTO») * @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 и авторизировать пользователя.
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 и возвращать его в ответе.