【AngularJS/Symfony2.3】AngularJS用のXSRFトークン発行/チェックサービス。

前回のエントリで若干触れたのでペタリ。

<?php

namespace Hoge\FugaBundle\Services;

use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\SessionCsrfProvider;

class XsrfTokenManager
{
    private $request;
    
    private $provider;
    
    protected $_cookieName = 'XSRF-TOKEN';
    
    protected $_sessionName = 'S-XSRF-TOKEN';
    
    const RECEIVED_HEADER_NAME = 'X-XSRF-TOKEN';
    
    /**
     * Construct
     */
    public function __construct(Request $request, SessionCsrfProvider $provider)
    {
        $this->request = $request;
        $this->provider = $provider;
    }
    
    /**
     * check xsrf token matches
     * 
     * @return boolean
     */
    public function check()
    {
        $_header = $this->request->headers->get(self::RECEIVED_HEADER_NAME);
        
        if (empty($_header) || $_header != $this->create()) {
            
            throw new \Symfony\Component\Security\Core\Exception\AccessDeniedException();
        }
        
        return true;
    }
    
    /**
     * generate strings of xsrf token
     * 
     * @param string $salt
     * @return \Hoge\FugaBundle\Services\XsrfTokenManager
     */
    private function create($salt = 'csrf_token')
    {
        $request = $this->request;
        
        $token = null;
        
        if ($request->getSession() && $request->getSession()->has($this->_sessionName)) {
            $token = $request->getSession()->get($this->_sessionName);
        } else {
            $token =  $this->provider->generateCsrfToken($salt);
            if ($request->getSession()) {
                $request->getSession()->set($this->_sessionName, $token);
            }
        }
        
        return $token;
    }

    /**
     * set cookie for client
     * 
     * @return boolean|\Symfony\Component\HttpFoundation\Cookie
     */
    public function set()
    {
        $cookie = new Cookie($this->_cookieName, $this->create(), 0, '/', null, false, false);
        
        $response = new Response();
        $response->headers->setCookie($cookie);
        $response->send();
        
        return $cookie;
    }
    
    /**
     * setter for cookie name
     * 
     * @param unknown $name
     * @return \Hoge\FugaBundle\Services\XsrfTokenManager
     */
    public function setCookieName($name)
    {
        $this->_cookieName = $name;
        
        return $this;
    }
    
    /**
     * getter for cookie name
     * 
     * @return string
     */
    public function getCookieName()
    {
        return $this->_cookieName;
    }
    
    /**
     * setter for session name
     * 
     * @param unknown $name
     * @return \Hoge\FugaBundle\Services\XsrfTokenManager
     */
    public function setSessionName($name)
    {
        $this->_sessionName = $name;
        
        return $this;
    }
    
    /**
     * getter for session name
     * 
     * @return string
     */
    public function getSessionName()
    {
        return $this->_sessionName;
    }
    
}

これをService.ymlで下記のように定義してやり
※前回のエントリそのまんま。

services:
    xsrf.token.manager:
        class: Hoge\FugaBundle\Services\XsrfTokenManager
        arguments: [@request, @form.csrf_provider]
        scope: request
        tags:
            - { name: xsrf.token.manager }

レスポンスリスナあたりでset()メソッドを実行してやればOK。

services:
    listener.before.filter:
        class: Hoge\FugaBundle\EventListener\ResponseListener
        arguments: [@xsrf.token.manager]
        scope: request
        tags:
            - { name: kernel.event_listener, event: kernel.response, method: createToken }

ResponseListenerは下記の通り。

\Hoge\FugaBundle\EventListener\ResponseListener.php

<?php
namespace Hoge\FugaBundle\EventListener;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Hoge\FugaBundle\Services\XsrfTokenManager;

class ResponseListener
{
    
    protected $manager;
    
    /**
     * Construct
     */
    public function __construct(XsrfTokenManager $manager)
    {
        $this->manager = $manager;
    }
    
    /**
     * before filter event
     */
    public function createToken()
    {
        $this->manager->set();
    }
    
}

あとはトークンチェックが必要なコントローラーのアクション先頭でcheck()メソッドを呼んでやればOK。
※本来はここもリスナーでやったほうがなおベター。