首页 > 解决方案 > Symfony 4 Security - 如何配置自定义 Guard 身份验证器?

问题描述

我已经在安全控制器中设置了自己的登录表单。使用我在安全配置中配置的 LoginForm。我想使用自定义登录表单身份验证器来更好地控制身份验证进度,在系统中注册登录信息,并执行我想要添加的任何操作(IP 检查等...)

所以我的应用程序中还有一个 LoginFormAuthenticator 类。不知何故,身份验证过程似乎甚至没有使用自定义 LoginFormAuthenticator 的方法。我的 security.yaml 配置是否正确?如何让我的所有配置一起工作?

symfony 的安全性在某些时候看起来很混乱,我无法开始理解人们是如何设法正确配置它的。

登录表单验证器:

class LoginFormAuthenticator extends AbstractGuardAuthenticator
{
    /**
     * Constructor
     *
     * @param Logger                       $logger
     * @param LoginAttemptManagerInterface $loginAttemptManager
     * @param LocationManagerInterface     $locationManager
     * @param RouterInterface              $router
     * @param UserPasswordEncoderInterface $userPasswordEncoder
     * @param UserRepositoryInterface      $userRepository
     */
    public function __construct(Logger $logger, LoginAttemptManagerInterface $loginAttemptManager, LocationManagerInterface $locationManager, RouterInterface $router, UserPasswordEncoderInterface $userPasswordEncoder, UserRepositoryInterface $userRepository)
    {
        $this->_logger              = $logger;
        $this->_loginAttemptManager = $loginAttemptManager;
        $this->_locationManager     = $locationManager;
        $this->_router              = $router;
        $this->_userPasswordEncoder = $userPasswordEncoder;
        $this->_userRepository      = $userRepository;
    }

    /**
     * {@inheritdoc}
     */
    protected function getLoginUrl()
    {
        return $this->_router->generate("login");
    }

    /**
     * {@inheritdoc}
     */
    public function getCredentials(Request $request)
    {
        $credentials = $request->get("login_form");

        return [
            "username" => $credentials["username"],
            "password" => $credentials["password"],
            "token"    => $credentials["_token"],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $username = $credentials["username"];

        try {
            $user = $this->_userRepository->findOneByUsername($username);

            if (null !== $user && $user instanceof UserInterface) {
                /* @var LoginAttempt $loginAttempt */
                $loginAttempt = $this->_loginAttemptManager->create();

                $user->addLoginAttempt($loginAttempt);
            }
        }
        catch (NoResultException $e) {
            return null;
        }
        catch (NonUniqueResultException $e) {
            return null;
        }
        catch (UsernameNotFoundException $e) {
            return null;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function checkCredentials($credentials, UserInterface $user)
    {
        /* @var string $rawPassword the unencoded plain password */
        $rawPassword = $credentials["password"];

        if ($this->_userPasswordEncoder->isPasswordValid($user, $rawPassword)) {
            return true;
        }

        return new CustomUserMessageAuthenticationException("Invalid credentials");
    }

    /**
     * {@inheritdoc}
     */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        /* @var AbstractUser $user */
        $user = $token->getUser();

        /* @var LoginAttempt $loginAttempt */
        $loginAttempt = $user->getLastLoginAttempt();

        $loginAttempt->success();

        this->_loginAttemptManager->saveOne($loginAttempt, true);
    }

    /**
     * {@inheritdoc}
     */
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        // without this method the authentication process becomes a loop
    }

    /**
     * {@inheritdoc}
     */
    public function start(Request $request, AuthenticationException $authException = null)
    {
        return new RedirectResponse($this->getLoginUrl());
    }

    /**
     * {@inheritdoc}
     */
    public function supports(Request $request)
    {
        return $request->getPathInfo() != $this->getLoginUrl() || !$request->isMethod(Request::METHOD_POST);
    }

    /**
     * {@inheritdoc}
     */
    public function supportsRememberMe()
    {
        return true;
    }
}

安全控制器:

class SecurityController extends AbstractController
{
    /**
     * @Route(path = "login", name = "login", methods = {"GET", "POST"})
     * @Template(template = "security/login.html.twig")
     *
     * @param AuthenticationUtils $authUtils
     * @param Request             $request
     * @return array
     */
    public function login(AuthenticationUtils $authUtils, Request $request)
    {
        $form = $this->createLoginForm();

        if (null !== $authUtils->getLastAuthenticationError()) {
            $form->addError(new FormError(
                $this->_translator->trans("error.authentication.incorrect-credentials", [], "security")
            ));
        }

        if (null != $authUtils->getLastUsername()) {
            $form->setData([
                "username" => $authUtils->getLastUsername(),
            ]);
        }

        // settings are in config/packages/security.yaml
        // configuration authenticates user in login form authenticator service

        return [
            "backgroundImages" => $this->_backgroundImageManager->findAll(),
            "form"             => $form->createView(),
        ];
    }

    /**
     * @return FormInterface
     */
    private function createLoginForm() : FormInterface
    {
        $form = $this->createForm(LoginForm::class, null, [
            "action"    => $this->generateUrl("login"),
            "method"    => Request::METHOD_POST,
        ]);

        $form->add("submit", SubmitType::class, [
            "label"              => $this->_translator->trans("btn.login", [], "button"),
            "icon_name"          => "sign-in",
            "translation_domain" => false,
        ]);

        return $form;
    }
}

安全.yaml:

security:
    providers:

        user_provider:
            entity:
                class: App\Entity\Model\AbstractUser
                property: username

        oauth_provider:
            entity:
                class: App\Entity\Model\ApiClient
                property: name

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        # The API-Oauth-Token-Firewall must be above the API-firewall
        api_oauth_token:
            pattern: ^/api/oauth/token$
            security: false

        # The API-firewall must be above the Main-firewall
        api:
            pattern: ^/api/*
            security: true
            stateless: true
            oauth2: true
            provider: oauth_provider
            access_denied_handler: App\Service\Api\Security\ApiAccessDeniedHandler

        main:
            anonymous: true
            guard:
                authenticators:
                    - App\Service\Security\LoginFormAuthenticator

            access_denied_handler: App\Service\Security\AccessDeniedHandler

            provider: user_provider

            form_login:
                login_path: /login
                check_path: /login
                default_target_path: / #index

                username_parameter: "login_form[username]"
                password_parameter: "login_form[password]"

            logout:
                # the logout path overrides the implementation of the logout method
                # in the security controller
                path: /logout
                target: / #index

            remember_me:
                secret: '%kernel.secret%'
                lifetime: 43200 # 60 sec * 60 min * 12 hours
                path: /
                remember_me_parameter: "login_form[remember]"

    encoders:
        App\Entity\Model\AbstractUser:
            algorithm: bcrypt
            cost: 13

    access_control:
        # omitted from this question

    role_hierarchy:
        # omitted from this question

标签: symfonyauthenticationsymfony4symfony-security

解决方案


你是怎么想出的逻辑的LoginFormAuthenticator::supports()?这不应该是相反的吗:

return 'login' === $request->attributes->get('_route')
            && $request->isMethod('POST');

推荐阅读