Silex:带有自定义验证器的用户表单

Silex: user form with custom authenticator

我正在尝试为我的 Silex 应用程序构建自定义身份验证系统。

想法是所有身份验证都将由单独的服务完成。我花了一天时间尝试进入 Silex\Symfony 身份验证机制,但不能说我做得很好 :-\ 我发现这个 Custom Authentication System with Guard 教程对我来说很基础。将它用作地下室后,我开始根据自己的需要采用它。 它一直有效,直到我尝试添加 form 位。

目前我的主要问题是在尝试访问安全区域时它不会(或者我不明白如何做到这一点)重定向到登录 URL。我通过将硬编码重定向放入 TokenAuthenticator->start() 方法找到了一种解决方法,但它对我来说真的很难闻。此外,在这种情况下,它不会在成功验证后重定向到最初请求的 URL。

有人可以提示我我的代码有什么问题吗(除了在没有完全理解机制如何工作的情况下这样做;)或者至少描述 Silex 身份验证流程(从请求安全 URL 到成功后重定向授权)?请记住,我对 Symfony 完全不熟悉,所以他们的手册对我来说并不是很有帮助。

我的代码:

index.php

<?
// bootstrap stuff here...

use Symfony\Component\HttpFoundation\Request;

$app->get('/login', function(Request $request) use ($app) {
    return $app['twig']->render('login.twig', array(
        'error'         => $app['security.last_error']($request),
        'last_username' => $app['session']->get('_security.last_username'),
    ));
});

$app['app.token_authenticator'] = function ($app) {
    return new \CP\Classes\TokenAuthenticator($app['security.encoder_factory']);
};

$app['security.firewalls'] = array(
    'secured' => array(
        'pattern' => '^/safe/',
        'form' => array('login_path' => '/login', 'check_path' => '/safe/login_check'),
        'logout' => array('logout_path' => '/safe/logout', 'invalidate_session' => true),
        'guard' => array(
            'authenticators' => array(
                'app.token_authenticator'
            ),
        ),
        'users' => function () use ($app) {
            return new \CP\Classes\OMSUserProvider($app);
        },
    ),
);

$app->register( new Silex\Provider\SecurityServiceProvider() );

// blablabla 
$app->run();

TokenAuthenticator.php

<?php

namespace CP\Classes;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;

class TokenAuthenticator extends AbstractGuardAuthenticator {
    private $encoderFactory;

    public function __construct( EncoderFactoryInterface $encoderFactory ) {
        $this->encoderFactory = $encoderFactory;
    }

    public function getCredentials( Request $request ) {
        $username = $request->request->get('_username');
        $request->getSession()->set(Security::LAST_USERNAME, $username);
        $password = $request->request->get('_password');

        return array(
            'username' => $username,
            'password' => $password
        );
    }

    public function getUser( $credentials, UserProviderInterface $userProvider ) {
        return $userProvider->loadUserByUsername( $credentials['username'] );
    }

    public function checkCredentials( $credentials, UserInterface $user ) {
        $res = json_decode(file_get_contents('http://127.0.0.1:83/auth.php?u='.$user->getUsername().'&p='.$credentials['password']));

        return $res->result;
    }

    public function onAuthenticationSuccess( Request $request, TokenInterface $token, $providerKey ) {
        return;
    }

    public function onAuthenticationFailure( Request $request, AuthenticationException $exception ) {
        $data = [ 'message' => strtr( $exception->getMessageKey(), $exception->getMessageData() )];

        return new JsonResponse( $data, 403 );
    }

    /**
     * Called when authentication is needed, but it's not sent
     */
    public function start( Request $request, AuthenticationException $authException = null ) {
//      Mentioned hardcoded redirect
//      $app = new \Silex\Application();
//      return $app->redirect('/login', 401);

        $data = [ // you might translate this message
                  'message' => 'Authentication Required', ];

        return new JsonResponse( $data, 401 );
    }

    public function supportsRememberMe() {
        return false;
    }
}

谢谢!

虽然不是完整的答案,但这里有一些想法:

  1. 您真的应该尝试了解安全组件。对我来说最好的方法是创建一个简单的应用程序(2 个页面:登录表单和安全区域),然后逐步调试。我也很喜欢这个演讲:https://www.youtube.com/watch?v=C1y6fxetP5k&list=PLo7mBDsRHu12SbjRS_botIIdJ51zU0FxP&index=11
  2. 不,Symfony 和 Silex 的安全性并没有什么不同。主要区别在于配置部分
  3. 你的实现很正确,但是创建一个新的Application实例没有意义。您的服务只需要重定向 URL,您可以注入:

TokenAuthenticator.php

<?php

//...
use Symfony\Component\HttpFoundation\RedirectResponse;

class TokenAuthenticator extends AbstractGuardAuthenticator
{
    private $encoderFactory;

    private $login_url

    public function __construct(EncoderFactoryInterface $encoderFactory , $login_url = '/login')
    {
        $this->encoderFactory = $encoderFactory;
        $this->login_url = $login_url;
    }

    //...

    /**
     * Called when authentication is needed, but it's not sent
     */
    public function start(Request $request, AuthenticationException $authException = null)
    {
        return new RedirectResponse($this->login_url, 401);
    }

    //...
}

index.php

<?php

//...
$app->get('/login', function (Request $request) use ($app) {
    return $app['twig']->render('login.twig', array(
        'error'         => $app['security.last_error']($request),
        'last_username' => $app['session']->get('_security.last_username'),
    ));
})->bind('login');

//...

$app['app.token_authenticator'] = function ($app) {
    return new \CP\Classes\TokenAuthenticator(
        $app['security.encoder_factory'],
        $app['url_generator']->generate('login')
    );
};

//...