使用外部REST API在Symfony 3中进行自定义身份验证

Kir*_*hxh 6 authentication passwords login symfony

我想编写一个基本的登录表单,通过向外部REST API发送请求来验证用户身份.如果凭据正确,外部API将接收登录名/密码并返回200(ok).但是,我无法通过UserProviderInterface实现它,因为外部REST API在回复中为我提供了密码.(我无法在loadUserByUsername方法中填写用户密码).

我在这里找到了一个有效的解决方案,但是它使用了Symfony 3中删除的类:Web服务的Symfony2自定义连接

我使用自定义Authenticator进行了测试,它只检查密码是"toto",但我得到了一个重定向循环,我的虚拟UserProvider仍被调用:

<?php
namespace AppBundle\Security\User;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimpleFormAuthenticatorInterface;

class WebserviceAuthenticator implements SimpleFormAuthenticatorInterface
{
    private $encoder;

    public function __construct(UserPasswordEncoderInterface $encoder)
    {
        $this->encoder = $encoder;
    }

    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        $user = new WebserviceUser($token->getUsername(), $token->getCredentials(), null, ['ROLE_ADMIN']);

        // HERE : call the external REST API
        if ($token->getCredentials() === 'toto') {
            $token = new UsernamePasswordToken(
                $user,
                $user->getPassword(),
                'main',
                $user->getRoles()
            );
            return $token;
        }
        throw new CustomUserMessageAuthenticationException('Invalid username or password');
    }

    public function supportsToken(TokenInterface $token, $providerKey)
    {
        return $token instanceof UsernamePasswordToken
        && $token->getProviderKey() === $providerKey;
    }

    public function createToken(Request $request, $username, $password, $providerKey)
    {
        return new UsernamePasswordToken($username, $password, $providerKey);
    }
}
Run Code Online (Sandbox Code Playgroud)

Kir*_*hxh 7

我得到了它的实现:

security.yml

providers:
    webservice:
        id: AppBundle\Security\User\WebserviceUserProvider

encoders:
    AppBundle\Entity\WebserviceUser: plaintext

firewalls:
    # disables authentication for assets and the profiler, adapt it according to your needs
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false

    main:
        anonymous: ~
        provider: webservice
        pattern: ^/
        form_login:
            check_path: login
            login_path: login
            use_forward: true
        logout: ~
        guard:
            authenticators:
                - app.webservice_authenticator

    login:
        pattern: ^/login$
        anonymous: ~

access_control:
  - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
  - { path: ^/cache, roles: IS_AUTHENTICATED_ANONYMOUSLY }
  - { path: ^/, roles: ROLE_USER }

role_hierarchy:
    ROLE_ADMIN:       ROLE_USER
Run Code Online (Sandbox Code Playgroud)

services.yml

services:
    app.webservice_authenticator:
        class: AppBundle\Security\User\WebserviceAuthenticator
Run Code Online (Sandbox Code Playgroud)

用户提供商

namespace AppBundle\Security\User;

use AppBundle\Entity\WebserviceUser;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

class WebserviceUserProvider implements UserProviderInterface
{
    public function loadUserByUsername($username)
    {
        return new WebserviceUser($username, null, null, ['ROLE_USER']);
    }

    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof WebserviceUser) {
            throw new UnsupportedUserException(
                sprintf('Instances of "%s" are not supported.', get_class($user))
            );
        }
        return $user;
    }

    public function supportsClass($class)
    {
        return WebserviceUser::class === $class;
    }
}
Run Code Online (Sandbox Code Playgroud)

认证

<?php

namespace AppBundle\Security\User;

use AppBundle\Service\RestClient;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;


class WebserviceAuthenticator extends AbstractFormLoginAuthenticator
{
    private $container;
    private $restClient;

    public function __construct(ContainerInterface $container, RestClient $restClient)
    {
        $this->container = $container;
        $this->restClient = $restClient;
    }

    public function getCredentials(Request $request)
    {
        if ($request->getPathInfo() != '/login' || $request->getMethod() != 'POST') {
            return;
        }

        $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)
    {
        //dump($credentials); die();
        if (array_key_exists('username', $credentials) == false) {
            return null;
        }
        $username = $credentials['username'];
        $password = strtoupper($credentials['password']);
        if ($username == '') {
            return null;
        }

        // Here the business code, provide your own implementtion
        if ($this->restClient->IsValidLogin($username, $password)) {
            return new WebserviceUser($username, $password, null, ['ROLE_USER']);
        } else {
            throw new CustomUserMessageAuthenticationException('Invalid credentials');
        }
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        return true;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        // AJAX! Return some JSON
        if ($request->isXmlHttpRequest()) {
            return new JsonResponse(array('message' => $exception->getMessageKey()), 403);
        }

        // for non-AJAX requests, return the normal redirect
        return parent::onAuthenticationFailure($request, $exception);
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        // AJAX! Return some JSON
        if ($request->isXmlHttpRequest()) {
            return new JsonResponse(array('userId' => $token->getUser()->getId()));
        }

        // for non-AJAX requests, return the normal redirect
        return parent::onAuthenticationSuccess($request, $token, $providerKey);
    }

    protected function getLoginUrl()
    {
        return $this->container->get('router')
        ->generate('login');
    }

    protected function getDefaultSuccessRedirectUrl()
    {
        return $this->container->get('router')
        ->generate('homepage');
    }
}
Run Code Online (Sandbox Code Playgroud)

诀窍似乎是:

  1. 在身份验证器的getUser方法中实现密码验证,并且checkCredentials方法始终返回true.
  2. 禁用UserProvider的refreshUser方法