在会话到期时响应 HTTP_UNAUTHORIZED/Redirect

Responding with HTTP_UNAUTHORIZED/Redirect on session expiration

在我的应用程序中,我在 routes.php 文件中定义了一个 before() 挂钩:

$app->before(function(Request $request) use($app) {

    $authAnon = $app['security']->isGranted('IS_AUTHENTICATED_ANONYMOUSLY');

    if(!$request->getSession()->get('username') && $authAnon) {

        if($request->isXmlHttpRequest()) {
            // return 401/HTTP_UNAUTHORIZED response
            $response = new Response();
            $response->setStatusCode(Response::HTTP_UNAUTHORIZED);
            $response->headers->set('Reason', 'SESSION_EXPIRED');
            $response->headers->set('WWW-Authenticate', 'MyAuthScheme realm="app:login"');
            return $response;
        }

        return new RedirectResponse('login', 301);

    }

}

但这导致 $app['security'] 不是 found/defined:

InvalidArgumentException in Container.php line 96: Identifier "security" is not defined.

我的安全设置如下所示:

$app->register(new Silex\Provider\SecurityServiceProvider(), array(
    'security.firewalls' => array(
        'login' => array(
            'pattern' => '^/login$',
        ),
        'secured' => array(
            'pattern' => '^.*$',
            'form' => array('login_path' => '/login', 'check_path' => '/login_check'),
            'logout' => array('logout_path' => '/logout', 'invalidate_session' => true),
            'users' => array(
                'admin' => array('ROLE_ADMIN', 'hashed_pwd'),
                'user' => array('ROLE_USER', 'hashed_pwd'),
            ),
        ),
    )
));

$app['security.role_hierarchy'] = array(
    'ROLE_ADMIN' => array('ROLE_USER'),
);

$app['security.access_rules'] = array(
    array('^/admin', 'ROLE_ADMIN'),
    array('^.*$', ['ROLE_USER']),
);

会话和安全提供程序的注册顺序如下所示:

$config_path = __DIR__.'/../app/config/';
require_once $config_path.'session.php';
require_once $config_path.'security.php';
require_once $config_path.'routes/routes.php';

$app->run();

我做错了什么?

编辑

请看看我下面的回答,看看我最终得到了什么。

security 服务已被弃用,将从 Silex 2.0 中删除。如果要检查用户的角色,则需要 security.authorization_checker 服务。

$authAnon = $app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_ANONYMOUSLY');

由于您使用的是 master(不稳定)版本,您需要注意这些事情,或者改用稳定版本。

我猜你现在使用的是 >= 2.6+ 的 Symfony 安全组件版本,所以根据 Silex docs 你不能直接使用服务安全性(如 $app['security']) 并且您必须使用 授权检查器 服务 ($app['security.authorization_checker']):

<?php

$app->before(function(Request $request) use($app) {

  // This won't work with Symfony Security Component < 2.6
  // $authAnon = $app['security']->isGranted('IS_AUTHENTICATED_ANONYMOUSLY');

  // Under Symfony 2.6+
  $authAnon = $app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_ANONYMOUSLY');
  // ...
}

这是我最后得出的结论:

if($request->headers->has('XHR-Request')) {

    $isAuthFully = $app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_FULLY');

    if(!$isAuthFully and $request->get('_route') !== 'login') {

        $response = new Response();
        $response->setStatusCode(Response::HTTP_UNAUTHORIZED);
        $response->headers->set('Login-Redirect', '');
        $response->headers->set('WWW-Authenticate', 'AppAuthScheme realm="application:login"');

        return $response;

    }
}

我在客户端添加了自定义 XHR-Request header:

angular.module('documentsCatalogApp', []).config(httpConfig);

httpConfig.$inject = ['$httpProvider'];
function httpConfig($httpProvider) {
    $httpProvider.defaults.headers.common['XHR-Request'] = '';
}

Symfony 的 getRequest()->isXmlHttpRequest() 依赖于 X-Requested-With header 可能会被浏览器剥离,至少我看过一篇关于Firefox的报道。

然后我添加了拦截器:

// mount the config / interceptor to your Angular application
// .config(xhrLoginRedirectInterceptorConfig)
// .factory('xhrLoginRedirectInterceptor', xhrLoginRedirectInterceptor)

xhrLoginRedirectInterceptorConfig.$inject = ['$httpProvider'];
function xhrLoginRedirectInterceptorConfig($httpProvider) {
    $httpProvider.interceptors.push('xhrLoginRedirectInterceptor');
}

xhrLoginRedirectInterceptor.$inject = ['$q', '$window'];
function xhrLoginRedirectInterceptor($q, $window) {

    return {

        request: function(request) { return request; },
        response: function(response) { return response; },

        responseError: function(response) {

            var headersList = response.headers();
            var loginRequired = headersList && headersList.hasOwnProperty('Login-Redirect');

            if(response.status === 401 && loginRequired) {

                var url = "http://" + $window.location.host + baseUrl + "/login";
                $window.location.href = url;

            }

            return $q.reject(response);

        }

    }

}

参考资料

  1. https://whosebug.com/a/22681873/532675
  2. https://github.com/angular/angular.js/issues/11008
  3. https://whosebug.com/a/30889331/532675