Silex:获取有关防火墙之外路由的经过身份验证的用户信息

时间:2015-05-24 03:10:35

标签: php security symfony silex jwt

我正在使用Silex 2.0(我知道 - 它的开发版本尚未完全发布)以及CNAM的JWT安全提供程序(请参阅:https://github.com/cnam/security-jwt-service-provider)为我正在编写的开源应用程序编写API。 / p>

简而言之,我关心的用户有三种类型:

  1. 具有完全访问权限的全站点管理员(ROLE_ADMIN
  2. 委托人(ROLE_COMMISH)创建他们拥有的对象,并可以编辑自己的对象
  3. 访问只读信息的匿名用户。
  4. 因此,有三个路段与这些“角色”一起出现:

      管理员可以执行其超级行动的
    1. /admin/*
    2. /commish/*委员或管理员可以对其对象执行操作
    3. /*所有用户都可以阅读信息
    4. 我遇到的问题是,虽然我可以设置3个防火墙,每个防火墙一个,但在第3个路由类别(例如GET /object/1)中有时需要匿名访问,但是如果用户提供了有效的JWT令牌,我需要访问该用户,以便对我在回复中提交的数据执行一些额外的逻辑。

      正如我当前设置的那样(更多在我的配置下面),它是全有或全无:我要么将整个防火墙限制为只有具有某个角色的经过身份验证的用户,要么我将其打开给匿名用户(因此无法查看用户信息)。

      是否可以拥有任何人都可以点击的路线,但也可以看到已登录的用户?

      当前的安全配置:

      $app['users'] = function () use ($app) {
          return new UserProvider($app);
      };
      
      $app['security.jwt'] = [
          'secret_key' => AUTH_KEY,
          'life_time'  => 86400,
          'algorithm'  => ['HS256'],
          'options'    => [
              'header_name' => 'X-Access-Token'
          ]
      ];
      
      $app['security.firewalls'] = array(
        'login' => [
          'pattern' => 'login|register|verify|lostPassword|resetPassword',
          'anonymous' => true,
        ],
        'admin' => array(
          'pattern' => '^/admin',
          'logout' => array('logout_path' => '/logout'),
          'users' => $app['users'],
          'jwt' => array(
              'use_forward' => true,
              'require_previous_session' => false,
              'stateless' => true,
          )
        ),
        'commish' => array(
          'pattern' => '^/commish',
          'logout' => array('logout_path' => '/logout'),
          'users' => $app['users'],
          'jwt' => array(
              'use_forward' => true,
              'require_previous_session' => false,
              'stateless' => true,
          )
        )
      );
      
      $app['security.role_hierarchy'] = array(
        'ROLE_ADMIN' => array('ROLE_MANAGER'),
      );
      
      $app->register(new Silex\Provider\SecurityServiceProvider());
      $app->register(new Silex\Provider\SecurityJWTServiceProvider());
      

      此外,我尝试了另一种方法,我匹配单个防火墙下的所有路由,但然后使用securty.access_rules配置保护某些路由,但它不起作用。我尝试过的一个例子:

      $app['security.firewalls'] = array(
        'api' => array(
          'pattern' => '^/',
          'logout' => array('logout_path' => '/logout'),
          'anonymous' => true,
          'jwt' => array(
            'use_forward' => true,
            'require_previous_session' => false,
            'stateless' => true
          )
        )
      );
      
      $app['security.access_rules'] = array(
        array('^/admin', 'ROLE_ADMIN'),
        array('^/commish', 'ROLE_MANAGER'),
        array('^/', 'IS_AUTHENTICATED_ANONYMOUSLY')
      );
      

3 个答案:

答案 0 :(得分:7)

您可以使用$ app [' security.jwt.encoder']解码jwt并创建自定义trait并扩展路由对象或使用midddleware ee路由级别或更简单的方法是在应用程序级别使用中间件。我有类似的问题,这就是我解决它的方式,如下所示

离。

   $app->before(function (Request $request, Application $app) {                      

       $request->decodedJWT  = $app['security.jwt.encoder']-> 
        decode($request->headers->get('X-Access-Token'));

    }); 

然后你可以通过这个

访问解码的jwt表单
 $app->get('/object/1', function(Request $request) {

     $decodedJWT = $request->decodedJWT;

     // do whatever logic you need here 
  })

答案 1 :(得分:1)

所以:到目前为止,我还没有通过“正常”的方式发现这是可能的,这是令人失望的。我不会在下面将我的详细内容标记为“答案”几天,希望有人能够提出更好,更“官方”的方式来解决困境。

TL; DR:我手动检查访问令牌字符串的请求标头,然后使用JWT类解码令牌,以便在防火墙外的路由中加载用户帐户。这令人难以置信的hacky,感觉非常脏,但它是我目前看到的问题的唯一解决方案。

技术详情:首先,您必须从请求标头中获取令牌值。您的控制器方法将被移交Symfony\Component\HttpFoundation\Request对象,您可以从中访问$request->headers->get('X-Access-Token')。在大多数情况下,用户将不会被身份验证,因此这将是空的,您可以返回null。

如果不为空,则必须使用Silex的JWTEncoder实例解码令牌内容,创建JWTToken的新令牌实例,将上下文设置为编码器的解码值,最后您可以从所述令牌访问用户名属性 - 然后可以使用该属性来获取相应的用户记录。我想出的一个例子:

$request_token = $request->headers->get('X-Access-Token','');

if(empty($request_token)) {
  return null;
}

try {
  $decoded = $app['security.jwt.encoder']->decode($request_token);

  $token = new \Silex\Component\Security\Http\Token\JWTToken();
  $token->setTokenContext($decoded);

  $userName = $token->getTokenContext()->name;

  //Here, you'd use whatever "load by username" function you have at your disposal
}catch(\Exception $ex) {
  return null;
}

显然,任何调用此函数的代码都需要知道,因为请求在防火墙之外,有保证将返回一个用户(因此hacky try-catch只需返回null)就可以解除异常。

编辑:我在这里更新了代码以使用Silex的内置DI容器(由Pimple提供),因此无需手动创建JWT编码器的新实例。我也将@ user5117342的答案标记为正确答案,因为使用某种Silex中间件方法要强得多。

修改(2016年4月):使用更新的 cnam / security-jwt-service 2.1.0 以及 symfony / security 2.8 ,有一个小的更新,使代码更简单:

$request_token = $request->headers->get('X-Access-Token','');

if(empty($request_token)) {
  return null;
}

try {
  $decodedToken = $app['security.jwt.encoder']->decode($request_token);

  $userName = $decodedToken->name;

  //Here, you'd use whatever "load by username" function you have at your disposal
}catch(\Exception $ex) {
  return null;
}

较新的依赖关系的问题是JWTToken构造函数需要3个参数,这些参数在大多数服务层中很难获得,更不用说是非常不合适了。当我更新我的Composer依赖项时,我最终发现我实际上并不需要创建JWTToken来获取我需要的用户名。

当然,需要注意的是我只在公共(匿名)API路由上使用此方法为登录的用户提供一些细节 - 我的应用程序不处理敏感数据所以我不会过度关注防火墙之外的这条大道。在最坏的情况下,黑帽用户最终会看到他们通常不会看到的非敏感数据,但就是这样。所以YMMV。

答案 2 :(得分:0)

你必须使用正则表达式,例如

$app['security.firewalls'] = array(
   'login' => [
       'pattern' => 'login|register|oauth',
       'anonymous' => true,
   ],
   'secured' => array(
       'pattern' => '^/api|/admin|/manager',
       'logout' => array('logout_path' => '/logout'),
       'users' => $app['users'],
       'jwt' => array(
           'use_forward' => true,
           'require_previous_session' => false,
           'stateless' => true,
       )
   ),
);