检查用户是否在不安全的路由上通过身份验证

Check if the user is authenticated on not secured route

我正在尝试使用 Silex 中的 symofny/security 包构建一个简单的登录,但我在身份验证检查方面遇到了一个小问题。

结构:

/
/login
/admin
    /login_check
    /logout

为了到达 /admin 路由,用户需要经过身份验证,如果没有,他将被重定向到 /login 表单,然后按照建议,询问 admin/login_check 用于身份验证(这都是由 symofny 的安全防火墙提供的)。

防火墙配置:

'security.firewalls' => array(
    'login' => array(
        'pattern' => '^/login$',
    ),
    'admin' => array(
        'pattern' => '^/admin',
        'http' => true,
        'form' => array(
            'login_path' => '/login',
            'check_path' => '/admin/login'
        ),
        'logout' => array(
            'logout_path' => '/admin/logout',
            'invalidate_session' => true
        ),
        'users' => ...
    ),
)

一切正常,但用户可以输入 /login 路由,即使他已经通过身份验证,这还不错,但我想避免这种情况。我厌倦了检查用户身份验证状态,但我想它不起作用,因为我在 /login 控制器上检查它,它不在网站的 "secured" 区域。

代码如下:

public function index(Request $request, Application $app)
{
    if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) {
        return $app->redirect($app['url_generator']->generate('admin'));
    }
    return $app['twig']->render('login/index.twig', array(
        'error'         => $app['security.last_error']($request),
        'last_username' => $app['session']->get('_security.last_username'),
    ));
}

这会抛出一个错误:The token storage contains no authentication token. One possible reason may be that there is no firewall configured for this URL. 所以,问题是,有没有办法使用 symfony/security 本机(没有任何绕过)来做到这一点?或者是否有可能在安全区域内创建 /login 路由,即使用户未登录(例如,为 GET 请求设置例外)也可以访问它,这将解决问题?

更新

我还为 /login 页面添加了带有选项 anonymous: true 的防火墙配置,这导致不再抛出错误,但是,当我登录 /admin 路线,方法 isGranted('ROLE_ADMIN') 导致 true,而在 /login 路线上它导致 false(我仍然在那里登录)。

您可以通过转储当前令牌轻松了解安全组件的行为。

public function index(Request $request, Application $app)
{
    var_dump($application['security.token_storage']->getToken());
}

当你没有为登录页面设置anonymous选项时(默认为false):

null

当您将登录页面的 anonymous 选项设置为 true 时:

object(Symfony\Component\Security\Core\Authentication\Token\AnonymousToken)
    private 'secret' => string 'login' (length=5)
    private 'user' (Symfony\Component\Security\Core\Authentication\Token\AbstractToken) => string 'anon.' (length=5)
    private 'roles' (Symfony\Component\Security\Core\Authentication\Token\AbstractToken) => 
        array (size=0)
            empty
    private 'authenticated' (Symfony\Component\Security\Core\Authentication\Token\AbstractToken) => boolean true
    private 'attributes' (Symfony\Component\Security\Core\Authentication\Token\AbstractToken) => 
        array (size=0)
           empty

以下示例描述了您在初始代码示例中出现错误的原因。

如何共享安全令牌?

要在多个防火墙之间共享安全令牌,您必须设置相同的 Firewall Context

SecurityServiceProvider 为您使用模式 security.context_listener.<name> 声明的 protected 防火墙注册上下文侦听器。在您的示例中,它被注册为 security.context_listener.admin。因此,您要使用的上下文被命名为 admin.

防火墙上下文密钥也存储在会话中,因此每个使用它的防火墙都必须将其 stateless 选项设置为 false

此外,要在登录页面上调用身份验证,必须将 anonymous 选项设置为 true

'security.firewalls' => array(
    'login' => array(
        'context' => 'admin',
        'stateless' => false,
        'anonymous' => true,
        'pattern' => '^/login$',
    ),
    'admin' => array(
        'context' => 'admin',
        'stateless' => false,
        'pattern' => '^/admin',
        'http' => true,
        'form' => array(
            'login_path' => '/login',
            'check_path' => '/admin/login'
        ),
        'logout' => array(
            'logout_path' => '/admin/logout',
            'invalidate_session' => true
        ),
        'users' => ...
    ),
)

更改后,如果您登录管理面板和登录页面,您将获得 Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken 的实例作为令牌,并且 Symfony\Component\Security\Core\Authentication\Token\AnonymousToken 如果您尚未登录。

因此,您可以使用 $app['security.authorization_checker']->isGranted('ROLE_ADMIN') 安全地检查权限,并在两个防火墙上获得相同的结果。