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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<?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で下記のように定義してやり
※前回のエントリそのまんま。

1
2
3
4
5
6
7
services:
    xsrf.token.manager:
        class: Hoge\FugaBundle\Services\XsrfTokenManager
        arguments: [@request, @form.csrf_provider]
        scope: request
        tags:
            - { name: xsrf.token.manager }

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

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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。
※本来はここもリスナーでやったほうがなおベター。