> Symfony中文手册 > 如何保护你应用程序中的服务和方法

如何保护你应用程序中的服务和方法

在security文档中,你可以看到,如何通过从服务容器中请求 security.authorization_checker 服务并检查当前用户的角色(role),来 保护一个控制器:

1
2
3
4
5
6
7
8
9
// ...
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
 
public function helloAction($name)
{
    $this->denyAccessUnlessGrAnted('ROLE_ADMIN');
 
    // ...
}

你也可以通过把 security.authorization_checker 服务注入到 任意 服务来保护它。把依赖注入到服务的基本知识,参考 服务容器 一文。例如,假设你有一个 NewsletterManager 类,用于发送邮件,你希望将它的使用限制在那些拥有 ROLE_NEWSLETTER_ADMIN role的用户。在添加security之前,类看起来是这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
// src/AppBundle/Newsletter/NewsletterManager.PHP
namespace AppBundle\Newsletter;
 
class NewsletterManager
{
    public function sendNewsletter()
    {
        // ... where you actually do the work / 你在此处真正做事
    }
 
    // ...
}

你的目标,是在 sendNewsletter() 方法被调用时,去检查用户的role。迈出的第一步,是向该对象注入 security.authorization_checker 服务。若 进行安全检查,此举将会失去意义,这是构造器注入(constructor injection)的理想场合,它确保authorization checker对象在 NewLetterManager 类中可用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/AppBundle/Newsletter/NewsletterManager.php
 
// ...
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
 
class NewsletterManager
{
    protected $authorizationChecker;
 
    public function __construct(AuthorizationCheckerInterface $authorizationChecker)
    {
        $this->authorizationChecker = $authorizationChecker;
    }
 
    // ...
}

接下来,在你的服务定义中注入服务:

1
2
3
4
5
# app/config/services.yml
services:
    newsletter_manager:
        class:     AppBundle\Newsletter\NewsletterManager
        arguments: ['@security.authorization_checker']
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="newsletter_manager" class="AppBundle\Newsletter\NewsletterManager">
            <argument type="service" id="security.authorization_checker"/>
        </service>
    </services>
</container>
1
2
3
4
5
6
7
8
// app/config/services.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
 
$container->setDefinition('newsletter_manager', new Definition(
    'AppBundle\Newsletter\NewsletterManager',
    array(new Reference('security.authorization_checker'))
));

sendNewsletter() 方法被调用时,被注入服务将被用于去执行安全检查(security check):

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
namespace AppBundle\Newsletter;
 
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
// ...
 
class NewsletterManager
{
    protected $authorizationChecker;
 
    public function __construct(AuthorizationCheckerInterface $authorizationChecker)
    {
        $this->authorizationChecker = $authorizationChecker;
    }
 
    public function sendNewsletter()
    {
        if (false === $this->authorizationChecker->isGranted('ROLE_NEWSLETTER_ADMIN')) {
            throw new AccessDeniedException();
        }
 
        // ...
    }
 
    // ...
}

如果当前用户没有 ROLE_NEWSLETTER_ADMIN,他们会被提示登录。

使用Annotations来保护Methods ¶

你也可以使用可选的 JMSSecurityExtraBundle bundle,在任意服务中,使用annotation注释来保护method calls(类的方法调用)。这个bundle没有包含在Symfony标准版中,但你可以选择安装它。

要开启annotation功能,使用 security.secure_service tag来对你想要保护的服务来 打标签您想保护的服务(你还可以为所有服务自动开启此功能,见本文底部“+”区块):

1
2
3
4
5
6
# app/config/services.yml
services:
    newsletter_manager:
        class: AppBundle\Newsletter\NewsletterManager
        tags:
            -  { name: security.secure_service }
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="newsletter_manager" class="AppBundle\Newsletter\NewsletterManager">
            <tag name="security.secure_service" />
        </service>
    </services>
</container>
1
2
3
4
5
6
7
8
9
10
// app/config/services.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
 
$definition = new Definition(
    'AppBundle\Newsletter\NewsletterManager',
    // ...
));
$definition->addTag('security.secure_service');
$container->setDefinition('newsletter_manager', $definition);

然后你就可以使用annotation来实现相同的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace AppBundle\Newsletter;
 
use JMS\SecurityExtraBundle\Annotation\Secure;
// ...
 
class NewsletterManager
{
 
    /**
     * @Secure(roles="ROLE_NEWSLETTER_ADMIN")
     */
    public function sendNewsletter()
    {
        // ...
    }
 
    // ...
}

annotations 注释能工作,是因为为你的“要执行安全检查的类”创建了一个代理类(proxy class)。这意味着,你只能在 public 和 protected 方法中使用annotations ,而不能在 private 或标记成 final 的方法中使用它们。

JMSSecurityExtraBundle 还允许你保护方法的参数和返回值。参考 JMSSecurityExtraBundle 文档以了解更多。

启动注释功能给所有服务

当去保护服务中的一个方法时(如上例),你既可以单独对每个服务打标签,也可以一次性地对 所有 服务激活此功能。要这样做,把 secure_all_services 配置选项设为 true:

1
2
3
4
# app/config/config.yml
jms_security_extra:
    # ...
    secure_all_services: true
1
2
3
4
5
6
7
8
9
10
11
<!-- 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"
    xmlns:jms-security-extra="http://example.org/schema/dic/jms_security_extra"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd">
 
    <!-- ... -->
    <jms-security-extra:config secure-all-services="true" />
</container>
1
2
3
4
5
// app/config/config.php
$container->loadFromExtension('jms_security_extra', array(
    // ...
    'secure_all_services' => true,
));

这个方法的缺点是,如果激活,最初的页面加载可能会非常慢,这取决你定义了多少服务。