Für das SonataAdmin Bundle gibt es die Möglichkeit über Symofny Guards eigene Logik in den Loginprozess einzubauen, wie z.B.:
- Abwehr von Brute Force Angriffen durch eine maximale Anzahl von Login-Versuchen
In dem folgenden Beispiel habe ich einen Guard konfiguriert für den Administrationsbereich, der mitzählt, wie oft sich ein User falsch eingeloggt hat.
Der UserManager muss dann die Logik enthalten, um bei jedem User die Anzahl der Logins mitzuzählen und ihn ggf. auch zu bannen für eine bestimmte Zeit.
<?php namespace App\Security; use App\Core\UserManager; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator; class AdminAuthenticator extends AbstractFormLoginAuthenticator { /** * @var UserManager */ protected $userManager; /** * @var RouterInterface */ protected $router; /** * Constructor. * * @param UserManager $userManager * @param RouterInterface $router */ public function __construct( UserManager $userManager, RouterInterface $router ) { $this->userManager = $userManager; $this->router = $router; } /** * Return the URL to the login page. * * @return string */ protected function getLoginUrl() { return $this->router->generate('sonata_user_admin_security_login'); } /** * Does the authenticator support the given Request? * * If this returns false, the authenticator will be skipped. * * @param Request $request * * @return bool */ public function supports(Request $request) { return ($request->getPathInfo() == '/admin/login_check' && $request->getMethod() === 'POST'); } /** * Get the authentication credentials from the request and return them * as any type (e.g. an associate array). * * Whatever value you return here will be passed to getUser() and checkCredentials() * * For example, for a form login, you might: * * return [ * 'username' => $request->request->get('_username'), * 'password' => $request->request->get('_password'), * ]; * * Or for an API token that's on a header, you might use: * * return ['api_key' => $request->headers->get('X-API-TOKEN')]; * * @param Request $request * * @return mixed Any non-null value * * @throws \UnexpectedValueException If null is returned */ public function getCredentials(Request $request) { return [ 'username' => $request->request->get('_username'), 'password' => $request->request->get('_password'), ]; } /** * Return a UserInterface object based on the credentials. * * The *credentials* are the return value from getCredentials() * * You may throw an AuthenticationException if you wish. If you return * null, then a UsernameNotFoundException is thrown for you. * * @param mixed $credentials * @param UserProviderInterface $userProvider * * @throws AuthenticationException * * @return UserInterface|null */ public function getUser($credentials, UserProviderInterface $userProvider) { return $userProvider->loadUserByUsername($credentials['username']); } /** * Returns true if the credentials are valid. * * If any value other than true is returned, authentication will * fail. You may also throw an AuthenticationException if you wish * to cause authentication to fail. * * The *credentials* are the return value from getCredentials() * * @param mixed $credentials * @param UserInterface $user * * @return bool * * @throws AuthenticationException */ public function checkCredentials($credentials, UserInterface $user) { if ($this->userManager->checkUserIsAllowedToLogin($user)) { return $this->userManager->checkPasswordValid($credentials['password'], $user); } else { throw new CustomUserMessageAuthenticationException('user is banned for 1 hour'); } } /** * Called when authentication executed and was successful! * * This should return the Response sent back to the user, like a * RedirectResponse to the last page they visited. * * If you return null, the current request will continue, and the user * will be authenticated. This makes sense, for example, with an API. * * @param Request $request * @param TokenInterface $token * @param string $providerKey The provider (i.e. firewall) key * * @return Response|null */ public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) { $user = $token->getUser(); $this->userManager->onLoginSuccess($user); } /** * @param Request $request * @param AuthenticationException $exception * @return \Symfony\Component\HttpFoundation\RedirectResponse */ public function onAuthenticationFailure(Request $request, AuthenticationException $exception) { if ($exception instanceof BadCredentialsException) { $userName = $request->request->get('_username'); if (!empty($userName)) { $user = $this->userManager->getUserByUserName($userName); if (!empty($user)) { $this->userManager->incrementPasswordFailedCount($user); } } } return parent::onAuthenticationFailure($request, $exception); } }
security.yaml:
security: providers: fos_userbundle: id: App\Security\UserProvider admin: pattern: /admin(.*) context: user form_login: provider: fos_userbundle login_path: /admin/login use_forward: false check_path: /admin/login_check failure_path: null default_target_path: sonata_admin_dashboard logout: path: /admin/logout target: /admin/login anonymous: true guard: authenticators: - App\Security\AdminAuthenticator provider: fos_userbundle access_control: # Admin login page needs to be accessed without credential - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/admin/login_check$, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/admin/, role: ROLE_SONATA_ADMIN }
UserProvider:
<?php namespace App\Security; use App\Entity\User; use FOS\UserBundle\Model\UserInterface; use FOS\UserBundle\Model\UserManagerInterface; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\User\UserInterface as SecurityUserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; class UserProvider implements UserProviderInterface { /** * @var UserManagerInterface */ protected $userManager; /** * Constructor. * * @param UserManagerInterface $userManager */ public function __construct(UserManagerInterface $userManager) { $this->userManager = $userManager; } /** * {@inheritdoc} */ public function loadUserByUsername($username) { /** @var User $user */ $user = $this->findUser($username); if (!$user) { throw new BadCredentialsException(); } return $user; } /** * {@inheritdoc} */ public function refreshUser(SecurityUserInterface $user) { if (!$user instanceof UserInterface) { throw new UnsupportedUserException(sprintf('Expected an instance of FOS\UserBundle\Model\UserInterface, but got "%s".', get_class($user))); } if (!$this->supportsClass(get_class($user))) { throw new UnsupportedUserException(sprintf('Expected an instance of %s, but got "%s".', $this->userManager->getClass(), get_class($user))); } if (null === $reloadedUser = $this->userManager->findUserBy(array('id' => $user->getId()))) { throw new UsernameNotFoundException(sprintf('User with ID "%s" could not be reloaded.', $user->getId())); } return $reloadedUser; } /** * {@inheritdoc} */ public function supportsClass($class) { $userClass = $this->userManager->getClass(); return $userClass === $class || is_subclass_of($class, $userClass); } /** * Finds a user by username. * * This method is meant to be an extension point for child classes. * * @param string $username * * @return UserInterface|null */ protected function findUser($username) { return $this->userManager->findUserByUsername($username); } }
services.yaml:
App\Security\AdminAuthenticator: - '@App\Core\UserManager' - '@router' App\Security\UserProvider: - '@fos_user.user_manager'