将有状态应用程序与无状态 JWT 相结合
Combine a stateful application with stateless JWT
我们有一个通过 LDAP 和 fr3d LDAP 包进行身份验证的 FOSUserBundle 登录系统。它的行为类似于使用 sessions 的普通多页应用程序。我们还有几个 RESTful 端点使用 FOSRestbundle 和正常的 sessions 进行身份验证。但是,我们需要与外部应用程序共享一些端点。
我们设法使用 Lexik 包实现了 JWT。它returns一个令牌就好了。但是,我不知道让用户使用我们的登录表单获取此令牌的最佳方法,以便他们的请求可以在 header 或 session 中传递它。我的问题是如何让用户以有状态的方式登录到我们的应用程序,同时接收 JWT 并在 ajax 请求时将其传递给服务器。这样我就可以允许外部客户端直接连接到 API。下面是我的 symfony2 安全配置,security.yml:
security:
#erase_credentials: false
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
chain_provider:
chain:
providers: [my_user_provider, fr3d_ldapbundle]
in_memory:
memory:
users:
admin: { password: secret, roles: 'ROLE_ADMIN' }
my_user_provider:
id: app.custom_user_provider
fos_userbundle:
id: fos_user.user_provider.username
fr3d_ldapbundle:
id: fr3d_ldap.security.user.provider
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: IS_AUTHENTICATED_FULLY }
- { path: ^/api, role: IS_AUTHENTICATED_FULLY }
- { path: ^/api/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api_login:
pattern: ^/api/login
fr3d_ldap: ~
provider: chain_provider
anonymous: true
stateless: false
form_login:
check_path: /api/login_check
username_parameter: username
password_parameter: password
require_previous_session: false
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
provider: chain_provider
stateless: false
lexik_jwt:
throw_exceptions: true
create_entry_point: true
main:
pattern: ^/
fr3d_ldap: ~
form_login:
# provider: fos_userbundle
provider: chain_provider
always_use_default_target_path: true
default_target_path: /
csrf_provider: security.csrf.token_manager
logout: true
anonymous: true
switch_user: { role: ROLE_LIMS-BIOINFO}
编辑:
根据 Kévin 的回答,我决定实施自定义 Twig 扩展,以便在每次加载页面时为登录用户获取令牌:
AppBundle/Extension/JsonWebToken.php:
<?php
namespace AppBundle\Extension;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class JsonWebToken extends \Twig_Extension
{
/**
* @var ContainerInterface
*/
private $container;
/**
* @var JWTManagerInterface
*/
private $jwt;
public function __construct(ContainerInterface $container, JWTManagerInterface $jwt)
{
$this->container = $container;
$this->jwt = $jwt;
}
public function getName()
{
return 'json_web_token';
}
public function getFunctions()
{
return [
'json_web_token' => new \Twig_Function_Method($this, 'getToken')
];
}
public function getToken()
{
$user = $this->container->get('security.token_storage')->getToken()->getUser();
$token = $this->jwt->create($user);
return $token;
}
}
app/config/services.yml:
app.twig_jwt:
class: AppBundle\Extension\JsonWebToken
arguments: ["@service_container", "@lexik_jwt_authentication.jwt_manager"]
tags:
- { name: twig.extension }
app/Resources/views/layout.html.twig
<script>window.jsonWebToken = '{{ json_web_token() }}';</script>
app/Resources/modules/layout/app.js:
var jsonWebToken = window.jsonWebToken;
$.ajaxSetup({
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization","Bearer " + jsonWebToken);
}
});
到目前为止,这似乎运作良好。它让我的外部 API 用户和内部应用程序用户共享相同的身份验证方法。
由于 JWT 令牌必须存储在客户端(而不是在 cookie 中以防止 CSRF 攻击),您可以使用 LexikJWTAuthenticationBundle 提供的 lexik_jwt_authentication.jwt_manager
服务的 create
方法来生成登录后的令牌,然后将此令牌注入生成的 HTML.
中的 <script>
标记中
嘿,我最近遇到了同样的情况。
为了生成 JWT,我创建了一个重定向监听器
class RedirectListener implements EventSubscriberInterface
{
//the private variables go up here
public function __construct(\Twig_Environment $twig, TokenStorageInterface $sam, EntityManagerInterface $em, JWTTokenManagerInterface $JWTTokenManager)
{
$this->twig = $twig;
$this->sam = $sam;
$this->em = $em;
$this->JWTTokenManager = $JWTTokenManager;
}
public function kernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$route = $request->get('_route');
$routeParams = $request->get('_route_params');
$pathInfo = $request->getPathInfo();
$matchApp = preg_match('/\/app/', $pathInfo);
if ($event->isMasterRequest()) {
if ($matchApp) {
$token = $this->sam->getToken();
if ($token) {
/** @var User $user */
$user = $token->getUser();
if($user instanceof User){
$token = $this->JWTTokenManager->create($user);
$this->twig->addGlobal('jwt', $token);
}
}
}
}
return $event;
}
}
这帮助我将 JWT 引入我的 twig 模板(我使用我的基本模板来确保它出现在每个页面上)
{% if jwt is defined %}
<span class="hidden" id="jwt" data-jwt="{{ jwt ? jwt : 'null' }}"></span>
{% endif %}
现在使用高速公路 JS,我可以使用 JWT 进行订阅:
let jwt = $('#jwt').data('jwt');
let connection = new Connection({url:"ws://127.0.0.1:8080/ws", realm:"realm1"});
connection.onopen = (session) => {
function onevent(args) {
console.log("Event:", args[0])
}
session.subscribe(jwt, onevent);
}
connection.open();
接下来,服务器现在可以使用 JWT 从 JS 接收消息
public function __construct(ContainerInterface $container)
{
$router = new Router();
$realm = "realm1";
$router->addInternalClient(new Pusher($realm, $router->getLoop()));
$router->addTransportProvider(new RatchetTransportProvider("0.0.0.0", 8080));
try{
$router->start();
} catch (\Exception $exception){
var_dump($exception->getMessage());
}
}
我现在需要为路由器注册一个模块,它将充当侦听器并将消息发送回已注册的主题 (JWT)。
我还没有 100% 做到这一点,所以任何建议都将不胜感激,我会不断更新。
我们有一个通过 LDAP 和 fr3d LDAP 包进行身份验证的 FOSUserBundle 登录系统。它的行为类似于使用 sessions 的普通多页应用程序。我们还有几个 RESTful 端点使用 FOSRestbundle 和正常的 sessions 进行身份验证。但是,我们需要与外部应用程序共享一些端点。
我们设法使用 Lexik 包实现了 JWT。它returns一个令牌就好了。但是,我不知道让用户使用我们的登录表单获取此令牌的最佳方法,以便他们的请求可以在 header 或 session 中传递它。我的问题是如何让用户以有状态的方式登录到我们的应用程序,同时接收 JWT 并在 ajax 请求时将其传递给服务器。这样我就可以允许外部客户端直接连接到 API。下面是我的 symfony2 安全配置,security.yml:
security:
#erase_credentials: false
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
chain_provider:
chain:
providers: [my_user_provider, fr3d_ldapbundle]
in_memory:
memory:
users:
admin: { password: secret, roles: 'ROLE_ADMIN' }
my_user_provider:
id: app.custom_user_provider
fos_userbundle:
id: fos_user.user_provider.username
fr3d_ldapbundle:
id: fr3d_ldap.security.user.provider
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: IS_AUTHENTICATED_FULLY }
- { path: ^/api, role: IS_AUTHENTICATED_FULLY }
- { path: ^/api/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api_login:
pattern: ^/api/login
fr3d_ldap: ~
provider: chain_provider
anonymous: true
stateless: false
form_login:
check_path: /api/login_check
username_parameter: username
password_parameter: password
require_previous_session: false
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
provider: chain_provider
stateless: false
lexik_jwt:
throw_exceptions: true
create_entry_point: true
main:
pattern: ^/
fr3d_ldap: ~
form_login:
# provider: fos_userbundle
provider: chain_provider
always_use_default_target_path: true
default_target_path: /
csrf_provider: security.csrf.token_manager
logout: true
anonymous: true
switch_user: { role: ROLE_LIMS-BIOINFO}
编辑:
根据 Kévin 的回答,我决定实施自定义 Twig 扩展,以便在每次加载页面时为登录用户获取令牌:
AppBundle/Extension/JsonWebToken.php:
<?php
namespace AppBundle\Extension;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class JsonWebToken extends \Twig_Extension
{
/**
* @var ContainerInterface
*/
private $container;
/**
* @var JWTManagerInterface
*/
private $jwt;
public function __construct(ContainerInterface $container, JWTManagerInterface $jwt)
{
$this->container = $container;
$this->jwt = $jwt;
}
public function getName()
{
return 'json_web_token';
}
public function getFunctions()
{
return [
'json_web_token' => new \Twig_Function_Method($this, 'getToken')
];
}
public function getToken()
{
$user = $this->container->get('security.token_storage')->getToken()->getUser();
$token = $this->jwt->create($user);
return $token;
}
}
app/config/services.yml:
app.twig_jwt:
class: AppBundle\Extension\JsonWebToken
arguments: ["@service_container", "@lexik_jwt_authentication.jwt_manager"]
tags:
- { name: twig.extension }
app/Resources/views/layout.html.twig
<script>window.jsonWebToken = '{{ json_web_token() }}';</script>
app/Resources/modules/layout/app.js:
var jsonWebToken = window.jsonWebToken;
$.ajaxSetup({
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization","Bearer " + jsonWebToken);
}
});
到目前为止,这似乎运作良好。它让我的外部 API 用户和内部应用程序用户共享相同的身份验证方法。
由于 JWT 令牌必须存储在客户端(而不是在 cookie 中以防止 CSRF 攻击),您可以使用 LexikJWTAuthenticationBundle 提供的 lexik_jwt_authentication.jwt_manager
服务的 create
方法来生成登录后的令牌,然后将此令牌注入生成的 HTML.
<script>
标记中
嘿,我最近遇到了同样的情况。 为了生成 JWT,我创建了一个重定向监听器
class RedirectListener implements EventSubscriberInterface
{
//the private variables go up here
public function __construct(\Twig_Environment $twig, TokenStorageInterface $sam, EntityManagerInterface $em, JWTTokenManagerInterface $JWTTokenManager)
{
$this->twig = $twig;
$this->sam = $sam;
$this->em = $em;
$this->JWTTokenManager = $JWTTokenManager;
}
public function kernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$route = $request->get('_route');
$routeParams = $request->get('_route_params');
$pathInfo = $request->getPathInfo();
$matchApp = preg_match('/\/app/', $pathInfo);
if ($event->isMasterRequest()) {
if ($matchApp) {
$token = $this->sam->getToken();
if ($token) {
/** @var User $user */
$user = $token->getUser();
if($user instanceof User){
$token = $this->JWTTokenManager->create($user);
$this->twig->addGlobal('jwt', $token);
}
}
}
}
return $event;
}
}
这帮助我将 JWT 引入我的 twig 模板(我使用我的基本模板来确保它出现在每个页面上)
{% if jwt is defined %}
<span class="hidden" id="jwt" data-jwt="{{ jwt ? jwt : 'null' }}"></span>
{% endif %}
现在使用高速公路 JS,我可以使用 JWT 进行订阅:
let jwt = $('#jwt').data('jwt');
let connection = new Connection({url:"ws://127.0.0.1:8080/ws", realm:"realm1"});
connection.onopen = (session) => {
function onevent(args) {
console.log("Event:", args[0])
}
session.subscribe(jwt, onevent);
}
connection.open();
接下来,服务器现在可以使用 JWT 从 JS 接收消息
public function __construct(ContainerInterface $container)
{
$router = new Router();
$realm = "realm1";
$router->addInternalClient(new Pusher($realm, $router->getLoop()));
$router->addTransportProvider(new RatchetTransportProvider("0.0.0.0", 8080));
try{
$router->start();
} catch (\Exception $exception){
var_dump($exception->getMessage());
}
}
我现在需要为路由器注册一个模块,它将充当侦听器并将消息发送回已注册的主题 (JWT)。
我还没有 100% 做到这一点,所以任何建议都将不胜感激,我会不断更新。