1
+ .. index ::
2
+ single: Security; User Provider
3
+
1
4
How to create a custom User Provider
2
5
====================================
3
6
4
- Symfony firewalls depend for their authentication on user providers.
5
- These providers are requested by the authentication layer to provide a
6
- user object for a given username. Symfony will check whether the
7
- password of this user is correct and will then generate a security token,
8
- so the user may stay authenticated during the current session. Out of
9
- the box, Symfony has an "in_memory" and an "entity" user provider.
10
- In this entry we'll see how you can create your own user provider.
11
- This may be a custom type of database, file or, in this example ,
12
- a webservice.
13
-
14
- Create a user class
7
+ Part of Symfony's standard authentication process depends on " user providers".
8
+ When a user submits a username and password, the authentication layer asks
9
+ the configured user provider to return a user object for a given username.
10
+ Symfony then checks whether the password of this user is correct and generates
11
+ a security token so the user stays authenticated during the current session.
12
+ Out of the box, Symfony has an "in_memory" and an "entity" user provider.
13
+ In this entry we'll see how you can create your own user provider, which
14
+ could be useful if your users are accessed via a custom database, a file ,
15
+ or - as we show in this example - a web service.
16
+
17
+ Create a User Class
15
18
-------------------
16
19
17
- First create the user class for your specific type of user. The class should
18
- implement :class: `Symfony\\ Component\\ Security\\ Core\\ User\\ UserInterface `.
20
+ First, regardless of *where * your user data is coming from, you'll need to
21
+ create a ``User `` class that represents that data. The ``User `` can look
22
+ however you want and contain any data. The only requirement is that the
23
+ class implements :class: `Symfony\\ Component\\ Security\\ Core\\ User\\ UserInterface `.
19
24
The methods in this interface should therefore be defined in the custom user
20
- class:
21
-
22
- ``getRoles() ``
23
- Return an array with the role(s) for this user, e.g. ``array('ROLE_USER') ``.
24
-
25
- ``getPassword() ``
26
- Return the password of the user (this would better be an encrypted password,
27
- see below).
28
-
29
- ``getSalt() ``
30
- Return the "salt" that should be used for encrypting the password. Return
31
- nothing (i.e. ``null ``) when the password needs no salt.
32
-
33
- ``getUsername() ``
34
- Return the username of this user, i.e. the name by which it can authenticate.
35
-
36
- ``eraseCredentials() ``
37
- Return nothing, but erase sensitive data from the user object. Don't erase
38
- credentials.
39
-
40
- ``equals(UserInterface $user) ``
41
- The first argument of this method is an object which implements ``UserInterface ``.
42
- When called upon a user, this method returns ``true `` when the given user is the same
43
- as itself, or ``false `` if it is not. This means you should compare several
44
- crucial properties like username, password and salt. Symfony uses this method to
45
- find out if the user should reauthenticate.
46
-
47
- .. code-block :: php
25
+ class: ``getRoles() ``, ``getPassword() ``, ``getSalt() ``, ``getUsername() ``,
26
+ ``eraseCredentials() ``, ``equals() ``.
27
+
28
+ Let's see this in action::
48
29
30
+ // src/Acme/WebserviceUserBundle/Security/User.php
49
31
namespace Acme\WebserviceUserBundle\Security\User;
50
-
32
+
51
33
use Symfony\Component\Security\Core\User\UserInterface;
52
-
34
+
53
35
class WebserviceUser implements UserInterface
54
36
{
55
37
private $username;
56
38
private $password;
57
39
private $salt;
58
40
private $roles;
59
-
41
+
60
42
public function __construct($username, $password, $salt, array $roles)
61
43
{
62
44
$this->username = $username;
63
45
$this->password = $password;
64
46
$this->salt = $salt;
65
47
$this->roles = $roles;
66
48
}
67
-
49
+
68
50
public function getRoles()
69
51
{
70
52
return $this->roles;
71
53
}
72
-
54
+
73
55
public function getPassword()
74
56
{
75
57
return $this->password;
76
58
}
77
-
59
+
78
60
public function getSalt()
79
61
{
80
62
return $this->salt;
81
63
}
82
-
64
+
83
65
public function getUsername()
84
66
{
85
67
return $this->username;
86
68
}
87
-
69
+
88
70
public function eraseCredentials()
89
71
{
90
72
}
91
-
73
+
92
74
public function equals(UserInterface $user)
93
75
{
94
76
if (!$user instanceof WebserviceUser) {
95
77
return false;
96
78
}
97
-
79
+
98
80
if ($this->password !== $user->getPassword()) {
99
81
return false;
100
82
}
101
-
83
+
102
84
if ($this->getSalt() !== $user->getSalt()) {
103
85
return false;
104
86
}
105
-
87
+
106
88
if ($this->username !== $user->getUsername()) {
107
89
return false;
108
90
}
109
-
91
+
110
92
return true;
111
93
}
112
94
}
113
95
114
- Create a user provider
96
+ If you have more information about your users - like a "first name" - then
97
+ you can add a ``firstName `` field to hold that data.
98
+
99
+ For more details on each of the methods, see :class: `Symfony\\ Component\\ Security\\ Core\\ User\\ UserInterface `.
100
+
101
+ Create a User Provider
115
102
----------------------
116
103
117
- Next we will create a user provider, in this case a ``WebserviceUserProvider ``.
118
- It provides the firewall with instances of ``WebserviceUser ``. The user provider
119
- has to implement the
120
- :class: `Symfony\\ Component\\ Security\\ Core\\ User\\ UserProviderInterface `,
121
- which requires three methods to be defined:
122
-
123
- ``loadUserByUsername($username) ``
124
- Does the actual loading of the user: it looks for a user with the given username
125
- in any way that seems appropriate to it and returns a user object (in our example
126
- a ``WebserviceUser ``). If the user was not found, this method must throw a
127
- ``UsernameNotFoundException ``.
128
-
129
- ``refreshUser(UserInterface $user) ``
130
- Refreshes the information of the given user. It must check if the given user object
131
- is an instance of the user class that is supported by this specific user provider.
132
- If not, an ``UnsupportedUserException `` should be thrown.
133
-
134
- ``supportsClass($class) ``
135
- Should return ``true `` if this user provider can handle users of the given class,
136
- ``false `` if not.
137
-
138
- .. code-block :: php
104
+ Now that we have a ``User `` class, we'll create a user provider, which will
105
+ grab user information from some web service, create a ``WebserviceUser `` object,
106
+ and populate it with data.
139
107
108
+ The user provider is just a plain PHP class that has to implement the
109
+ :class: `Symfony\\ Component\\ Security\\ Core\\ User\\ UserProviderInterface `,
110
+ which requires three methods to be defined: ``loadUserByUsername($username) ``,
111
+ ``refreshUser(UserInterface $user) ``, and ``supportsClass($class) ``. For
112
+ more details, see :class: `Symfony\\ Component\\ Security\\ Core\\ User\\ UserProviderInterface `.
113
+
114
+ Here's an example of how this might look::
115
+
116
+ // src/Acme/WebserviceUserBundle/Security/User/WebserviceUserProvider.php
140
117
namespace Acme\WebserviceUserBundle\Security\User;
141
-
118
+
142
119
use Symfony\Component\Security\Core\User\UserProviderInterface;
143
120
use Symfony\Component\Security\Core\User\UserInterface;
144
-
145
121
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
146
122
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
147
-
123
+
148
124
class WebserviceUserProvider implements UserProviderInterface
149
125
{
150
126
public function loadUserByUsername($username)
151
127
{
152
- try {
153
- // make a call to your webservice here:
154
- // throw a WebserviceUserNotFoundException (or something alike) if you did not find the requested user
155
- // $user = ...;
156
-
157
- return new WebserviceUser($user->getUsername(), $user->getPassword(), $user->getSalt(), $user->getRoles())
158
- } catch(WebserviceUserNotFoundException $e) {
128
+ // make a call to your webservice here
129
+ // $userData = ...
130
+ // pretend it returns an array on success, false if there is no user
131
+
132
+ if ($userData) {
133
+ // $password = '...';
134
+ // ...
135
+
136
+ return new WebserviceUser($username, $password, $salt, $roles)
137
+ } else {
159
138
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
160
139
}
161
140
}
162
-
141
+
163
142
public function refreshUser(UserInterface $user)
164
143
{
165
144
if (!$user instanceof WebserviceUser) {
166
145
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
167
146
}
168
-
147
+
169
148
return $this->loadUserByUsername($user->getUsername());
170
149
}
171
-
150
+
172
151
public function supportsClass($class)
173
152
{
174
153
return $class === 'Acme\WebserviceUserBundle\Security\User\WebserviceUser';
175
154
}
176
155
}
177
156
178
- Create a service for the user provider
157
+ Create a Service for the User Provider
179
158
--------------------------------------
180
159
181
160
Now we make the user provider available as service.
@@ -212,16 +191,21 @@ Now we make the user provider available as service.
212
191
213
192
$container->setDefinition('webservice_user_provider', new Definition('%webservice_user_provider.class%');
214
193
215
- .. note ::
194
+ .. tip ::
216
195
217
196
The real implementation of the user provider will probably have some
218
- dependencies or configuration options. Add these as arguments in the
219
- service definition.
197
+ dependencies or configuration options or other services. Add these as
198
+ arguments in the service definition.
199
+
200
+ .. note ::
220
201
221
- Modify ` security.yml `
222
- ---------------------
202
+ Make sure the services file is being imported. See :ref: ` service-container-imports-directive `
203
+ for details.
223
204
224
- In ``app/config/security.yml `` everything comes together. Add the user provider
205
+ Modify ``security.yml ``
206
+ -----------------------
207
+
208
+ In ``/app/config/security.yml `` everything comes together. Add the user provider
225
209
to the list of providers in the "security" section. Choose a name for the user provider
226
210
(e.g. "webservice") and mention the id of the service you just defined.
227
211
@@ -241,3 +225,25 @@ users, e.g. by filling in a login form. You can do this by adding a line to the
241
225
security :
242
226
encoders :
243
227
Acme\WebserviceUserBundle\Security\User\WebserviceUser : sha512
228
+
229
+ The value here should correspond with however the passwords were originally
230
+ encoded when creating your users (however those users were created). When
231
+ a user submits her password, the password is appended to the salt value and
232
+ then encoded using this algorithm before being compared to the hashed password
233
+ returned by your ``getPassword() `` method.
234
+
235
+ .. sidebar :: Specifics on how passwords are encoded
236
+
237
+ Symfony uses a specific method to combine the salt and encode the password
238
+ before comparing it to your encoded password. If ``getSalt() `` returns
239
+ nothing, then the submitted password is simply encoded using the algorithm
240
+ you specify in ``security.yml ``. If a salt *is * specified, then the following
241
+ value is created and *then * hashed via the algorithm:
242
+
243
+ ``$password.'{'.$salt.'}'; ``
244
+
245
+ If your external users have their passwords salted via a different method,
246
+ then you'll need to do a bit more work so that Symfony properly encodes
247
+ the password. That is beyond the scope of this entry, but would include
248
+ sub-classing ``MessageDigestPasswordEncoder `` and overriding the ``mergePasswordAndSalt ``
249
+ method.
0 commit comments