【问题标题】:Combine a stateful application with stateless JWT将有状态应用程序与无状态 JWT 相结合
【发布时间】:2015-10-07 00:08:44
【问题描述】:

我们有一个通过 LDAP 和 fr3d LDAP 包进行身份验证的 FOSUserBundle 登录系统。它的行为类似于使用会话的普通多页应用程序。我们还有几个使用 FOSRestbundle 和普通会话进行身份验证的 RESTful 端点。但是,我们需要与外部应用程序共享一些端点。

我们设法使用 Lexik 包实现了 JWT。它返回一个令牌就好了。但是,我不知道让用户使用我们的登录表单获取此令牌的最佳方法,以便他们的请求可以在标头或会话中传递它。我的问题是如何允许用户以有状态的方式登录我们的应用程序,同时接收 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 用户和内部应用程序用户共享相同的身份验证方法。

【问题讨论】:

    标签: symfony fosuserbundle


    【解决方案1】:

    由于JWT token 必须存储在客户端(而不是cookie 中以防止CSRF 攻击),您可以使用LexikJWTAuthenticationBundle 提供的lexik_jwt_authentication.jwt_manager 服务的create 方法在登录后生成token,然后将此令牌注入到生成的 HTML 中的 &lt;script&gt; 标记中。

    【讨论】:

    • 谢谢,这很有意义。我将在接下来的几天内试一试,如果可行,请接受您的回答。
    • 在脚本标签中注入令牌不是安全问题吗?例如,如果用户在登录后保存他的页面,它将在 html 中包含他的 jwt 令牌。然后他可以在不知道他正在共享他的令牌的情况下共享他保存的页面。
    【解决方案2】:

    嘿,我最近遇到了同样的情况。 为了生成 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();
    

    接下来服务器现在可以从 JS 接收带有 JWT 的消息

     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%,所以如果有任何建议,我将不胜感激,我会不断更新。

    【讨论】:

      猜你喜欢
      • 2018-12-29
      • 1970-01-01
      • 2011-06-19
      • 2018-05-02
      • 2011-04-08
      • 1970-01-01
      • 2010-11-23
      • 2011-07-16
      相关资源
      最近更新 更多