Я использую Silex 2.0 (я знаю — это версия для разработки и еще не полностью выпущена) вместе с провайдером безопасности CNW JWT (см .: https://github.com/cnam/security-jwt-service-provider) написать API для приложения с открытым исходным кодом, которое я пишу.
Короче говоря, есть три типа пользователей, которые мне небезразличны:
ROLE_ADMIN
) которые имеют полный доступROLE_COMMISH
) которые создают свои собственные объекты и могут редактировать свои собственныеТаким образом, есть три раздела маршрутов, которые соответствуют этим «ролям»:
/admin/*
где администраторы могут выполнять свои действия Uber/commish/*
где комиссары или администраторы могут выполнять свои действия на своих объектах/*
где все пользователи могут читать информациюПроблема, с которой я столкнулся, заключается в том, что, хотя я могу настроить 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')
);
Вы можете использовать $ app [‘security.jwt.encoder’], чтобы декодировать jwt и создавать собственные черта характера и расширение объекта маршрута или использование midddlewareЛибо на уровне маршрута, либо проще было бы использовать промежуточное ПО на уровне приложений. У меня была похожая проблема, и вот как я решил ее, что-то вроде ниже
ех.
$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
})
Итак: до сих пор я не нашел это возможным «нормальным» способом, который разочаровывает. Я не буду отмечать то, что я подробно описываю ниже, в качестве «ответа» в течение нескольких дней, надеясь, что кто-то сможет вмешаться и предложить лучший, более «официальный» способ решения этой дилеммы.
TL; DR: Я вручную проверяю заголовки запроса на строку токена доступа, затем декодирую токен с помощью классов JWT, чтобы загрузить учетную запись пользователя на маршрутах за пределами брандмауэра. Он невероятно хакерский, кажется совершенно грязным, но это единственное решение проблемы, которое я вижу на данный момент.
Технические детали: Во-первых, вы должны получить значение токена из заголовка запроса. Ваш метод контроллера будет передан Symfony\Component\HttpFoundation\Request
объект, из которого вы можете получить доступ $request->headers->get('X-Access-Token')
, В большинстве случаев пользователь не будет аутентифицирован, поэтому он будет пустым, и вы можете вернуть ноль.
Если не пусто, вы должны использовать экземпляр Silex JWTEncoder
чтобы декодировать содержимое токена, создайте новый экземпляр токена JWTToken
установите контекст для декодированного значения из кодировщика, и, наконец, вы можете получить доступ к свойству username из указанного токена — который затем может быть использован для получения соответствующей пользовательской записи. Пример того, что я придумал:
$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;
}
И, очевидно, любой код, вызывающий эту функцию, должен знать, что, поскольку запрос находится за пределами брандмауэра, существует нуль гарантировать, что пользователь будет возвращен (отсюда и хакерская попытка поймать, которая заглушает исключения, просто возвращая null
).
редактироватьЯ обновил код, чтобы использовать встроенный контейнер Silex DI (предоставленный Pimple), поэтому нет необходимости создавать новый экземпляр JWT-кодера вручную. Я также отмечаю правильный ответ @ user5117342, поскольку использование какого-либо промежуточного программного обеспечения Silex гораздо надежнее.
Изменить (апрель 2016 г.): Использование обновленного 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.
Вы должны использовать регулярное выражение, например,
$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,
)
),
);