> Symfony中文手册 > 如何使用Api密钥(key)认证用户

如何使用Api密钥(key)认证用户

译注:

超高难文档 Symfony security系统最难的地方就是手写security factory,这是一个五步的过程,搞清楚这五个手写的PHP文件,即意味着通达security核心。

简单说,在请求抵达防火墙之后,要经过Authentication Listener、User Provider、Authentication Provider等流程,直至完成认证。虽然所有这些已经被Symfony Guard所统括(照本宣科地实现接口功能即可解决问题),但如果你要基于Symfony去做更有野心的事(比如完成类似 HWIOauthBundleLaravel Passport 这种功能),还是要搞明白Symfony在以上步骤中究竟都做了些什么。

Authenticator Authenticator,是对token进行检查以完成认证(返回持有roles的token)的程序,它是一个服务,会用到User Provider。

相对于本文中所讲的 不完全自定义 认证,即,使用了Symfony自带的文件名包含Authenticator的一些抽象类或接口,这统统是简化版认证,可以让你少写几个页面。而Symfony Guard则是更进一步的最高集成版本,也就是最简化认证。

包括 真·完全自定义 认证在内(手写5个文件,包括security factory),它们之间的具体区别,大家在实战中一试便知——例如,开发微信服务号时,会用到本文涉及的 全部 内容(并且还不够用)。

使用Symfony security组件的 simple authenticator系列/guard系列 的话,微信身份认证的开发真乃“分分钟的事”。

参考 如何使用Guard创建一个自定义验证系统 以一个更简单、更灵活的方式来完成类似的自定义身份认证的任务。

如今,使用API key(例如在开发一个web service时)来认证用户是很普遍的事。API key在每次请求中都被提供,作为查询字符串参数(query string parameter )或通过 Http 头信息而传入。

API Key Authenticator ¶

基于请求信息来认证用户应通过预认证机制(pre-authentication mechanism)来完成。SimplePreAuthenticatorInterface 接口让你很容易地实现此类需求。

你遇到的真实场景可能有所不同,但在本例中,token的获取来自 apikey query参数,根据此值来加载相应的用户名,然后User对象得以创建:

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
// src/AppBundle/Security/ApiKeyAuthenticator.php
namespace AppBundle\Security;
 
use Symfony\Component\Httpfoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
 
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
    public function createToken(Request $request, $providerKey)
    {
        // look for an apikey query parameter
        // 寻找一个apikey query 参数
        $apiKey = $request->query->get('apikey');
 
        // or if you wAnt to use an "apikey" header, then do something like this:
        // 或者,若你想使用一个 “apikey” 头时:
        // $apiKey = $request->headers->get('apikey');
 
        if (!$apiKey) {
            throw new BadCredentialsException();
 
            // or to just skip api key authentication
            // 或者,直接跳过 api key 的认证
            // return null;
        }
 
        return new PreAuthenticatedToken(
            'anon.',
            $apiKey,
            $providerKey
        );
    }
 
    public function supportsToken(TokenInterface $token, $providerKey)
    {
        return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
    }
 
    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        if (!$userProvider instanceof ApiKeyUserProvider) {
            throw new \InvalidArgumentException(
                sprintf(
                    'The user provider must be an instance of ApiKeyUserProvider (%s was given).',
                    get_class($userProvider)
                )
            );
        }
 
        $apiKey = $token->getCredentials();
        $username = $userProvider->getUsernameForApiKey($apiKey);
 
        if (!$username) {
            // CAUTION: this message will be returned to the client
            // (so don't put any un-trusted messages / error strings here)
            // 注意:此异常信息将被返回至客户端
            // 请不要在这里放置任何敏感信息 / 错误字符串
            throw new CustomUserMessageAuthenticationException(
                sprintf('API Key "%s" does not exist.', $apiKey)
            );
        }
 
        $user = $userProvider->loadUserByUsername($username);
 
        return new PreAuthenticatedToken(
            $user,
            $apiKey,
            $providerKey,
            $user->getRoles()
        );
    }
}

一旦你 配置 好所有内容,就可以把 apikey 参数添加到查询字符串中,比如 http://example.com/admin/foo?apikey=37b51d194a7513e45b56f6524f2d51f2

认证过程需要若干步骤,你的具体实现可能有所不同:

1. createToken ¶

在请求周期的早期,Symfony调用 createToken()。此时你要做的就是去创建一个“包含了请求中的所有信息”的token对象,并针对此请求进行用户认证(比如通过 apikey query parameter) 。如果缺少这些信息,会抛出 BadCredentialsException 异常,从而导致认证失败。你可能想要返回 null 而不是跳过身份认证,这样 Symfony 就可以回退到另一种身份认证方式(authentication method),如果有的话。

若你从 createToken() 方法返回 null,确保在防火墙中开启了 anonymous。这样你就可以得到一个 AnonymousToken

2.supportsToken ¶

Symfony 调用 createToken() 之后,将调用你的类中的 supportsToken() 方法(以及任何其它的authentication listeners)来计算出应该由谁来处理(当前的)token。这只是一种 “允许多个身份认证机制用于同一防火墙” 的方式(通过这种方式,你可以先尝试使用certificate或者API key来认证用户,[未通过的话]然后再回滚到表单登录)。

多数情况下,对于由 createToken() 方法所创建的token,只需要去让方法返回true。你的逻辑应与本例颇为相似。

3. authenticateToken ¶

如果 supportsToken() 返回 true,Symfony即调用 authenticateToken()$userProvider 是一个关键之处,它是个外部的类,帮助你加载用户信息。后面你会了解更多。

在这个特定的例子中,以下事件发生在 authenticateToken() 中:

  1. 首先,你使用 $userProvider 来以某种方式查找 $apiKey 相对应的 $username

  2. 其次,你再次使用 $userProvider 来针对这个 $username 去加载或者创建一个 User 对象;

  3. 最后,你要创建一个 authenticated token (即,一个至少拥有一个role的token),它有你赋予的正确的roles,同时User对象也被赋予它。

最终目标是要用 $apiKey 去找出或创建一个 User 对象。具体你要 如何 找出(如,查询数据库),以及你的 User对象之细节,是多样化的。这些不同,在你的user provider中体现的尤为明显。

User Provider ¶

$userProvider 可以是任何user provider(参考 如何创建自定义的User Provider )。本例中,$apiKey 用来以某种方式针对用户进行“用户名查找”。这是由 getUsernameForApiKey() 方法完成的,在我们这个使用场景下,它被完全自定义地创建出来(即,它并非Symfony内核中的user provider系统中的某个方法)。

$userProvider 可能如下面所示:

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
// src/AppBundle/Security/ApiKeyUserProvider.php
namespace AppBundle\Security;
 
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
 
class ApiKeyUserProvider implements UserProviderInterface
{
    public function getUsernameForApiKey($apiKey)
    {
        // Look up the username based on the token in the database, via
        // an API call, or do something entirely different
        // 通过对 API 进行调用,或是某些完全不同的东东,来基于数据库中的token查出用户名
        $username = ...;
 
        return $username;
    }
 
    public function loadUserByUsername($username)
    {
        return new User(
            $username,
            null,
            // the roles for the user - you may choose to determine
            // these dynamically somehow based on the user
            // 用户的roles - 根据用户的不同,你可以选择 “动态地确定” 这些roles
            array('ROLE_API')
        );
    }
 
    public function refreshUser(UserInterface $user)
    {
        // this is used for storing authentication in the session
        // but in this example, the token is sent in each request,
        // so authentication can be stateless. Throwing this exception
        // is proper to make things stateless
        // 此方法用于在session中存储认证信息
        // 但在本例中,token在每一次请求中被发送,
        // 因此认证信息是stateless(无状态)的。
        // 抛出这个异常,对于stateless来说,是最适合的
        throw new UnsupportedUserException();
    }
 
    public function supportsClass($class)
    {
        return 'Symfony\Component\Security\Core\User\User' === $class;
    }
}

现在,把user provider注册为服务:

1
2
3
4
# app/config/services.yml
services:
    api_key_user_provider:
        class: AppBundle\Security\ApiKeyUserProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- app/config/services.xml -->
<?xml version="1.0" ?>
<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="api_key_user_provider"
            class="AppBundle\Security\ApiKeyUserProvider" />
    </services>
</container>
1
2
3
4
5
// app/config/services.php
 
// ...
$container
    ->register('api_key_user_provider', 'AppBundle\Security\ApiKeyUserProvider');

在独立章节 如何创建自定义的User Provider 中进学习。

getUsernameForApiKey() 中的逻辑是由你来决定的。通过在一个(存下了) "token" (的)数据表中获取信息,你可以把API key(如 37b51d)通过某种方式转换成用户名(如 jondoe)。

上面的过程同样适用于 loadUserByUsername()。本例中,创建的只是Symfony的核心 User 类。当你不需要在User对象中存储任何额外的信息时(如 firstName),它才有意义(译注:内置User类用于简单场合)。否则,你应该创建 你自己的 User类,再通过查询数据库来装载User。如此你就在 User 对象中添加自定义数据了。

最后,只需确保 supportsClass() 方法对User对象返回 true,这个User类,与你在 loadUserByUsername() 中所返回的相同。

如果你的认证过程如同本例一样是stateless的(即,你希望用户在每次请求中发送API key,因此你毋须把登录信息存到session中了),所以你可以直接在 refreshUser() 中抛出 UnsupportedUserException 异常。

若你真的想在 session 中存储认证数据,以便不必在每次请求中都发送key,参考(在Session中存储Authentication信息)。

认证失败的处理 ¶

当凭据不正确或者身份认证失败时,为了能让你的 ApiKeyAuthenticator 正确的显示 401 http 状态,你应该在authenticator中实现 AuthenticationFailureHandlerInterface 接口。这便提供了一个 onAuthenticationFailure 方法供你创建一个错误信息的Response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/AppBundle/Security/ApiKeyAuthenticator.php
namespace AppBundle\Security;
 
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
 
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
{
    // ...
 
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        return new Response(
            // this contains information about *why* authentication failed
            // use it, or return your own message
            // 这里包括了关于 *为何* 认证失败的信息,使用此异常,或返回你自己的信息
            strtr($exception->getMessageKey(), $exception->getMessageData()),
            401
        );
    }
}

配置 ¶

一旦你完成了 ApiKeyAuthenticator 的所有设置,你需要把它注册成服务并在security配置信息中(security.yml)使用它,首先,注册为服务。

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

    apikey_authenticator:
        class:  AppBundle\Security\ApiKeyAuthenticator
        public: false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- app/config/config.xml -->
<?xml version="1.0" ?>
<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="apikey_authenticator"
            class="AppBundle\Security\ApiKeyAuthenticator"
            public="false" />
    </services>
</container>
1
2
3
4
5
6
7
8
9
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
 
// ...
 
$definition = new Definition('AppBundle\Security\ApiKeyAuthenticator');
$definition->setPublic(false);
$container->setDefinition('apikey_authenticator', $definition);

现在,在security配置中的 firewall 节点下,分别使用 simple_preauthprovider 选项键来激活此服务和你的自定义user provider(参考 如何创建自定义的User Provider):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# app/config/security.yml
security:
    # ...

    firewalls:
        secured_area:
            pattern: ^/api
            stateless: true
            simple_preauth:
                authenticator: apikey_authenticator
            provider: api_key_user_provider

    providers:
        api_key_user_provider:
            id: api_key_user_provider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 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>
        <!-- ... -->
 
        <firewall name="secured_area"
            pattern="^/api"
            stateless="true"
            provider="api_key_user_provider"
        >
            <simple-preauth authenticator="apikey_authenticator" />
        </firewall>
 
        <provider name="api_key_user_provider" id="api_key_user_provider" />
    </config>
</srv:container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// app/config/security.php
 
// ..
 
$container->loadFromExtension('security', array(
    'firewalls' => array(
        'secured_area'       => array(
            'pattern'        => '^/api',
            'stateless'      => true,
            'simple_preauth' => array(
                'authenticator'  => 'apikey_authenticator',
            ),
            'provider' => 'api_key_user_provider',
        ),
    ),
    'providers' => array(
        'api_key_user_provider'  => array(
            'id' => 'api_key_user_provider',
        ),
    ),
));

如果你也定义了 access_control, 确保添加一个新的入口:

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

    access_control:
        - { path: ^/api, roles: ROLE_API }
1
2
3
4
5
6
7
8
9
10
<!-- 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">
 
    <rule path="^/api" role="ROLE_API" />
</srv:container>
1
2
3
4
5
6
7
8
9
// app/config/security.php
$container->loadFromExtension('security', array(
    'access_control' => array(
        array(
            'path' => '^/api',
            'role' => 'ROLE_API',
        ),
    ),
));

就是这样!现在,在每次请求开始的时候,你的 ApiKeyAuthenticator 会被调用,然后你的身份认证过程将会展开。

stateless 配置选项防止Symfony将用户的认证信息存在session中,因为每次请求都发送了 apikey,再行存储就没有必要了。如果你真的 需要在session中存储认证信息的话,继续阅读!

在Session中存储Authentication ¶

目前为止,本节讲解的是,每一次请求都会送出某种authentication token。但在某些情况下(如 OAuth 流程),token仅在一次 请求中被发送。这时,你对用户进行认证后会把authentication信息存在session中,以便用户在后续的每一次请求中自动登录。

要想实现这一功能,首先从防火墙中删除 stateless 选项键,或者把它设为 false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# app/config/security.yml
security:
    # ...

    firewalls:
        secured_area:
            pattern: ^/api
            stateless: false
            simple_preauth:
                authenticator: apikey_authenticator
            provider: api_key_user_provider

    providers:
        api_key_user_provider:
            id: api_key_user_provider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 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>
        <!-- ... -->
 
        <firewall name="secured_area"
            pattern="^/api"
            stateless="false"
            provider="api_key_user_provider"
        >
            <simple-preauth authenticator="apikey_authenticator" />
        </firewall>
 
        <provider name="api_key_user_provider" id="api_key_user_provider" />
    </config>
</srv:container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// app/config/security.php
 
// ..
$container->loadFromExtension('security', array(
    'firewalls' => array(
        'secured_area'       => array(
            'pattern'        => '^/api',
            'stateless'      => false,
            'simple_preauth' => array(
                'authenticator'  => 'apikey_authenticator',
            ),
            'provider' => 'api_key_user_provider',
        ),
    ),
    'providers' => array(
        'api_key_user_provider' => array(
            'id' => 'api_key_user_provider',
        ),
    ),
));

即便 token 被存在 session 中,凭据(credentials) - 本例中是API key (即 $token->getCredentials()) - 出于安全原因,并不会被存在 session 中。要利用 session,更新 ApiKeyAuthenticator 来查看被存储的token是否有一个可以使用的有效User对象:

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
// src/AppBundle/Security/ApiKeyAuthenticator.php
 
// ...
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
    // ...
    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        if (!$userProvider instanceof ApiKeyUserProvider) {
            throw new \InvalidArgumentException(
                sprintf(
                    'The user provider must be an instance of ApiKeyUserProvider (%s was given).',
                    get_class($userProvider)
                )
            );
        }
 
        $apiKey = $token->getCredentials();
        $username = $userProvider->getUsernameForApiKey($apiKey);
 
        // User is the Entity which represents your user
        // User 是能够呈现你的用户的 Entity
        $user = $token->getUser();
        if ($user instanceof User) {
            return new PreAuthenticatedToken(
                $user,
                $apiKey,
                $providerKey,
                $user->getRoles()
            );
        }
 
        if (!$username) {
            // this message will be returned to the client
            // 此信息将返回客户端
            throw new CustomUserMessageAuthenticationException(
                sprintf('API Key "%s" does not exist.', $apiKey)
            );
        }
 
        $user = $userProvider->loadUserByUsername($username);
 
        return new PreAuthenticatedToken(
            $user,
            $apiKey,
            $providerKey,
            $user->getRoles()
        );
    }
    // ...
}

把认证信息存到session时的工作原理是这样的:

  1. 在每一次请求结束时,Symfony会序列化token对象(从 authenticateToken()返回),同时也会对 User对象(因为它被设为了token的一个属性)序列化;

  2. 在下一次请求中token将被反序列化,同时,被反序列化的 User 对象将被传入user provider的 refreshUser() 函数。

第二步是重要的一步: Symfony 将调用 refreshUser() 方法并把 “序列化到session中的User对象” 传给你。如果你的用户是存储在数据库中,那么你可能希望新查询一个新鲜版本(fresh version)的user以确保它没有过期。但是,不管你的需求是什么,refreshUser() 现在应当返回User对象:

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
// src/AppBundle/Security/ApiKeyUserProvider.php
 
// ...
class ApiKeyUserProvider implements UserProviderInterface
{
    // ...
 
    public function refreshUser(UserInterface $user)
    {
        // $user is the User that you set in the token inside authenticateToken()
        // after it has been deserialized from the session
        // 此处的 $user ,是它从 session 中被反序列化之后,
        // 你在 authenticateToken() 中设置在 token 中的 User 对象
 
 
        // you might use $user to query the database for a fresh user
        // 你可能会使用 $user 来查询数据库,以获取用户的最新内容
        // $id = $user->getId();
        // use $id to make a query / 据此 $id 进行查询
 
        // if you are *not* reading from a database and are just creating
        // a User object (like in this example), you can just return it
        // 如果你  *不* 从数据库中读取用户,
        // 只是创建一个 User 对象(如同本例),则直接返回它
        return $user;
    }
}

你可能还需要确保 User 对象被正确地序列化。如果你的 User对象 包含了private属性,PHP无法对其序列化。这时,你可以取回一个“相关属性都被设为 null ”的User对象。例程参考 如何从数据库中(Entity Provider)加载Security用户。

只对特定URL进行认证 ¶

本节假定你想在 每一次 请求中查找 apikey 认证信息。但在某些情况下(如一个OAuth流程),你只在用户抵达了一个特定的URL(如 OAuth时的重定向URL)的时候才需要查找其的认证信息。

幸运的是,处理这种情况也很容易: 只需要在 createToken() 方法在创建token之前去检查一下当前的URL是什么即可:

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
// src/AppBundle/Security/ApiKeyAuthenticator.php
 
// ...
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\HttpFoundation\Request;
 
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
    protected $httpUtils;
 
    public function __construct(HttpUtils $httpUtils)
    {
        $this->httpUtils = $httpUtils;
    }
 
    public function createToken(Request $request, $providerKey)
    {
        // set the only URL where we should look for auth information
        // and only return the token if we're at that URL
        // 在此设置我们应当查找auth信息的唯一URL
        // 然后仅当我们处于该URL时才返回token
        $targetUrl = '/login/check';
        if (!$this->httpUtils->checkRequestPath($request, $targetUrl)) {
            return;
        }
 
        // ...
    }
}

在这里使用了极为好用的 HttpUtils 类来检查当前 URL 是否与你想要获取的 URL 相匹配。此时,URL (/login/check) 在类中被写死了,但是你也可以把它作为构造函数的第二个参数来进行注入。

接下来,只需更新你的服务配置,注入 security.http_utils 服务即可:

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

    apikey_authenticator:
        class:     AppBundle\Security\ApiKeyAuthenticator
        arguments: ["@security.http_utils"]
        public:    false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- app/config/config.xml -->
<?xml version="1.0" ?>
<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="apikey_authenticator"
            class="AppBundle\Security\ApiKeyAuthenticator"
            public="false"
        >
            <argument type="service" id="security.http_utils" />
        </service>
    </services>
</container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
 
// ...
 
$definition = new Definition(
    'AppBundle\Security\ApiKeyAuthenticator',
    array(
        new Reference('security.http_utils')
    )
);
$definition->setPublic(false);
$container->setDefinition('apikey_authenticator', $definition);

就是这样!Have fun!