Symfony 4安全性-如何配置自定义Guard身份验证器?

时间:2019-09-13 15:27:24

标签: symfony authentication symfony4 symfony-security

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

所以我的应用程序中还有一个LoginFormAuthenticator类。某种程度上,身份验证过程似乎甚至没有使用以下方法 定制的LoginFormAuthenticator。我的security.yaml是否正确配置?如何使所有配置一起使用?

在某些时候,symfony中的安全性似乎很混乱,我无法理解人们如何正确配置它。

LoginFormAuthenticator:

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;
    }
}

SecurityController:

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;
    }
}

security.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

2 个答案:

答案 0 :(得分:0)

您是如何提出LoginFormAuthenticator::supports()的逻辑的? 这样不应该相反:

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

答案 1 :(得分:0)

显然,我在security.yaml中配置了两种身份验证形式

因此,我从配置中删除了form_login键:

security.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

            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]"

并更新了LoginFormAuthenticator -集成 -还添加了检查CSRF令牌

LoginFormAuthenticator

class LoginFormAuthenticator extends AbstractGuardAuthenticator
{
    const FORM       = "login_form";
    const USERNAME   = "username";
    const PASSWORD   = "password";
    const CSRF_TOKEN = "token";

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

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

    /**
     * Get Target URL
     *
     * @return string
     */
    protected function getTargetUrl()
    {
        return $this->_router->generate("index");
    }

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

        $request->getSession()->set(Security::LAST_USERNAME, $credentials["username"]);

        return [
            self::USERNAME   => $credentials["username"],
            self::PASSWORD   => $credentials["password"],
            self::CSRF_TOKEN => $credentials["_token"],
        ];
    }

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

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

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

                $user->addLoginAttempt($loginAttempt);
            }

            return $user;
        }
        catch (NoResultException $e) {
            throw new BadCredentialsException("Authentication failed");
        }
        catch (NonUniqueResultException $e) {
            throw new BadCredentialsException("Authentication failed");
        }
    }

    /**
     * {@inheritdoc}
     */
    public function checkCredentials($credentials, UserInterface $user)
    {
        $csrfToken = new CsrfToken(self::FORM, $credentials[self::CSRF_TOKEN]);

        if (false === $this->_csrfTokenManager->isTokenValid($csrfToken)) {
            throw new InvalidCsrfTokenException('Invalid CSRF token');
        }

        /* @var string $rawPassword the unencoded plain password */
        $rawPassword = $credentials[self::PASSWORD];

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

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

        if (null !== $loginAttempt) {
            $this->_loginAttemptManager->saveOne($loginAttempt);
        }

        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->setStatus(LoginAttempt::STATUS_AUTHENTICATION_SUCCESS);

        if (null !== $loginAttempt) {
            $this->_loginAttemptManager->saveOne($loginAttempt);
        }

        return new RedirectResponse($this->getTargetUrl());
    }

    /**
     * {@inheritdoc}
     */
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception->getMessage());

        return new RedirectResponse($this->getLoginUrl());
    }

    /**
     * {@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;
    }
}