如何创建自定义的User Provider

3.4 版本
维护中的版本

Symfony标准认证过程中的一部分是取决于“user providers”。当用户提交用户名和密码时,认证层(authentication layer)会请求配置好的user provider返回给定用户名的user对象。Symfony接下来要检查用户的密码是否正确,并生成一个 security token,以便用户在当前会话期间能够保持认证过的身份。Symfony最牛之处,在于有四个user provider:in_memoryentityldapchain。在本节,你将看到如何创建自己的user provider,如果你透过一个自定义的数据库、一个文件、或者 - 如本例所展示的 - 一个web service来访问用户的话,它将非常有用。

创建User类 

首先,无论你的user数据从 哪里 获取,你都需要创建一个 User 类来呈现那些数据。 User 可以是你希望的任何样子,并且能容纳任何数据。唯一的要求,是这个类必须实现 UserInterface 接口。此接口中的方法应该在自定义的user类中进行定义: getRoles(), getPassword(), getSalt(), getUsername(), eraseCredentials()。去实现 EquatableInterface 接口可能也是有用的,它定义了一个方法,用于检查user是否等同于当前用户。此接口需要一个 isEqualTo() 方法。

这就是你的 WebserviceUser 类实际看起来的样子:

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
// src/AppBundle/Security/User/WebserviceUser.php
namespace AppBundle\Security\User;
 
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
 
class WebserviceUser implements UserInterface, EquatableInterface
{
    private $username;
    private $password;
    private $salt;
    private $roles;
 
    public function __construct($username, $password, $salt, array $roles)
    {
        $this->username = $username;
        $this->password = $password;
        $this->salt = $salt;
        $this->roles = $roles;
    }
 
    public function getRoles()
    {
        return $this->roles;
    }
 
    public function getPassword()
    {
        return $this->password;
    }
 
    public function getSalt()
    {
        return $this->salt;
    }
 
    public function getUsername()
    {
        return $this->username;
    }
 
    public function eraseCredentials()
    {
    }
 
    public function isEqualTo(UserInterface $user)
    {
        if (!$user instanceof WebserviceUser) {
            return false;
        }
 
        if ($this->password !== $user->getPassword()) {
            return false;
        }
 
        if ($this->salt !== $user->getSalt()) {
            return false;
        }
 
        if ($this->username !== $user->getUsername()) {
            return false;
        }
 
        return true;
    }
}

如果你有更多关于用户的信息 - 像是一个“first name” - 你可以添加一个 firstName 字段来持有该数据。

创建User Provider 

现在,你有了一个 User 类,你要创建一个user provider,它会从一些web service中获取用户信息,创建一个 WebserviceUser 对象,并对其填充数据。

user provider仅仅是一个实现了UserProviderInterface 接口的原生PHP类,它需要定义三个方法:loadUserByUsername($username), refreshUser(UserInterface $user)supportsClass($class)。更多细节,参考 UserProviderInterface

下例是它可能的样子:

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
// src/AppBundle/Security/User/WebserviceUserProvider.php
namespace AppBundle\Security\User;
 
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
 
class WebserviceUserProvider implements UserProviderInterface
{
    public function loadUserByUsername($username)
    {
        // make a call to your webservice here
        // 在此调用你的webservice
        $userData = ...
        // pretend it returns an array on success, false if there is no user
        // 假设在成功时返回的是一个数组,没有用户时则返回false
 
        if ($userData) {
            $password = '...';
 
            // ...
 
            return new WebserviceUser($username, $password, $salt, $roles);
        }
 
        throw new UsernameNotFoundException(
            sprintf('Username "%s" does not exist.', $username)
        );
    }
 
    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof WebserviceUser) {
            throw new UnsupportedUserException(
                sprintf('Instances of "%s" are not supported.', get_class($user))
            );
        }
 
        return $this->loadUserByUsername($user->getUsername());
    }
 
    public function supportsClass($class)
    {
        return $class === 'AppBundle\Security\User\WebserviceUser';
    }
}

把User Provider设为服务 

现在把user provider设为服务:

1
2
3
4
# app/config/services.yml
services:
    app.webservice_user_provider:
        class: AppBundle\Security\User\WebserviceUserProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- app/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd">
 
    <services>
        <service id="app.webservice_user_provider"
            class="AppBundle\Security\User\WebserviceUserProvider"
        />
    </services>
</container>
1
2
3
4
5
6
7
// app/config/services.php
use Symfony\Component\DependencyInjection\Definition;
 
$container->setDefinition(
    'app.webservice_user_provider',
    new Definition('AppBundle\Security\User\WebserviceUserProvider')
);

user provider的真正实现,可能还需要一些依赖或配置选项,以及其他服务。把所有这些当作参数传入服务定义中。

修改security.yml 

所有内容在你的security配置信息中汇集。把user provider添加到“security”根键下的的providers列表中。并且为这个user provider选择一个名称(如 “webservice”),把你刚才定义的服务标记为 id 键的值。

1
2
3
4
5
6
7
# app/config/security.yml
security:
    # ...

    providers:
        webservice:
            id: app.webservice_user_provider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- app/config/security.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:srv="http://symfony.com/schema/dic/services"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd">
 
    <config>
        <!-- ... -->
 
        <provider name="webservice" id="app.webservice_user_provider" />
    </config>
</srv:container>
1
2
3
4
5
6
7
8
9
10
// app/config/security.php
$container->loadFromExtension('security', array(
    // ...
 
    'providers' => array(
        'webservice' => array(
            'id' => 'app.webservice_user_provider',
        ),
    ),
));

Symfony还必须知道如何去加密网站用户们所提供的密码,如,在登录表单中填写的密码。你可以在security配置中添加一行“encoders”键:

1
2
3
4
5
6
# app/config/security.yml
security:
    # ...

    encoders:
        AppBundle\Security\User\WebserviceUser: bcrypt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- app/config/security.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:srv="http://symfony.com/schema/dic/services"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd">
 
    <config>
        <!-- ... -->
 
        <encoder class="AppBundle\Security\User\WebserviceUser"
            algorithm="bcrypt" />
    </config>
</srv:container>
1
2
3
4
5
6
7
8
9
// app/config/security.php
$container->loadFromExtension('security', array(
    // ...
 
    'encoders' => array(
        'AppBundle\Security\User\WebserviceUser' => 'bcrypt',
    ),
    // ...
));

当创建用户时(不管这些用户是如何创建的),不管密码是如何被加密的,此处的键值必须与该加密方式相对应。当用户提交其密码时,密码使用该算法加密,加密结果会和 getPassword() 方法所返回的“密码的hash值” 比对。

密码加密的细节

Symfony使用了特定的方法来添加salt,并且,在和“加密过的密码”进行比对之前,(把原始密码与salt结合)实施密码加密。若 getSalt() 什么也没有返回,则提交过来的密码只是单纯使用了你在 security.yml 中定义的算法进行了加密。如果 salt 指定,那么下面的值将被创建,然后 结合加密算法进行hash:

1
$password.'{'.$salt.'}'

如果外部用户把自己的密码以不同的方法进行salt添加,那你就需要做一些附加工作,以便Symfony对密码正确地加密。这超出了本文范畴,但却包含 MessageDigestPasswordEncoder 子类,同时复写了 mergePasswordAndSalt 方法。

此外,你可以配置用于密码加密的hash算法的细节。本例中,程序显式地设置了bcrypt hash算法的cost:

1
2
3
4
5
6
7
8
# app/config/security.yml
security:
    # ...

    encoders:
        AppBundle\Security\User\WebserviceUser:
            algorithm: bcrypt
            cost: 12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- app/config/security.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:srv="http://symfony.com/schema/dic/services"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd">
 
    <config>
        <!-- ... -->
 
        <encoder class="AppBundle\Security\User\WebserviceUser"
            algorithm="bcrypt"
            cost="12" />
    </config>
</srv:container>
1
2
3
4
5
6
7
8
9
10
11
// app/config/security.php
$container->loadFromExtension('security', array(
    // ...
 
    'encoders' => array(
        'AppBundle\Security\User\WebserviceUser' => array(
            'algorithm' => 'bcrypt',
            'cost' => 12,
        ),
    ),
));

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)