Примечания
Создан для проекта Symfony 3.4, должен быть совместим с Symfony 4, но я не тестировал;
Все службы имеют автоматическую привязку, поэтому добавлять нечего вservices.yml
Я не использую FOSUserBundle;
Я не соблюдаю стандарты кодирования Symfony;
Я делал заметки здесь и там;также я поместил некоторые комментарии в самом коде;
Важная часть в конце (LoginFormAuthenticator), я публикую весь код, надеюсь, кому-то будет легче, чем мне.
Источник вдохновения:
https://symfony.com/doc/3.4/security.html
https://symfonycasts.com/screencast/symfony3-security
https://www.sitepoint.com/easier-authentication-with-guard-in-symfony-3/
Стена кода
security.yml
Конфигурация безопасности
Для пользователя «memory» имя пользователя и пароль «admin»
security:
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
AppBundle\Entity\User:
algorithm: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
chain_provider:
chain:
providers: [memory_provider, db_provider]
memory_provider:
memory:
users:
admin:
password: '$2y$13$21gXkzksqlR68HhAYB2WLOqcQvJZzgIrSH/KRq1aEzkkOnjI7lR9e'
roles: 'ROLE_SUPER_ADMIN'
db_provider:
entity:
class: AppBundle:User
property: email
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: ~
pattern: ^/
logout:
path: /logout
target: /
guard:
authenticators:
- AppBundle\Security\LoginFormAuthenticator
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: ROLE_USER }
Шаблон app / Resources / views / Security / _content.login.html.twig
{% set form_action = path('security_login') %}
<form action="{{ form_action }}" method="post" autocomplete="off" id="login_f">
{% if error %}
<div class="login_form_error">{{ error.messageKey }}</div>
{% endif %}
<div class="closed">
<input type="hidden" name="_csrf_token" value="{{ csrf_token(login_csrf_token) }}" />
</div>
<div class="login_field login_field_0">
<label for="login_username" class="login_l">
<i class="fas fa-user"></i>
</label>
<input type="text" class="login_i" id="login_username" name="_username" placeholder="Username" />
</div>
<div class="login_field login_field_1">
<label for="login_password" class="login_l">
<i class="fas fa-key"></i>
</label>
<input type="password" class="login_i" id="login_password" name="_password" placeholder="Password" />
</div>
<div>
<input type="submit" class="login_bttn" id="_submit" value="Login" />
</div>
</form>
Template app / Resources / views / Security /login.html.twig
Нет необходимости в base.html.twig
{% extends 'base.html.twig' %}
{% block content %}
<div id="login_c">
{% include 'Security/_content.login.html.twig' %}
</div>
{% endblock %}
Служба
Отображает страницу входа илисодержимое входа
Замените постоянное значение CSRF_TOKEN своим собственным
namespace AppBundle\Services\User;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class LoginFormService {
private $templatingEngine;
private $authenticationUtils;
const CSRF_TOKEN = 'login:token:w4kSzA3v5VJyb4aWLbV7stAY92cNwgL77J6QrXpU!';
function __construct(
EngineInterface $templatingEngine,
AuthenticationUtils $authenticationUtils) {
$this -> templatingEngine = $templatingEngine;
$this -> authenticationUtils = $authenticationUtils;
}
function getHtml($contentOnly = False) {
// last username entered by the user
$lastUsername = $this -> authenticationUtils -> getLastUsername();
// get the login error if there is one
$error = $this -> authenticationUtils -> getLastAuthenticationError();
$html_vars = array(
'lastUsername' => $lastUsername,
'error' => $error,
'login_csrf_token' => self::CSRF_TOKEN,
);
$html_template = 'Security/login.html.twig';
if ( $contentOnly ) {
$html_template = 'Security/_content.login.html.twig';
}
$html = $this -> templatingEngine -> render($html_template, $html_vars);
return $html;
}
}
Контроллер входа
Простой контроллер буфера для отображения страницы входаесли пользователь получитess / login
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Services\User\LoginFormService;
class SecurityController extends Controller {
/**
@Route("/login", name="security_login")
*/
public function loginAction(LoginFormService $loginFormService, Request $request) {
return new Response($loginFormService -> getHtml());
}
/**
@Route("/logout", name="security_logout")
*/
public function logoutAction() {}
}
Аутентификатор Guard
Вместо "project_homepage_route" используйте любой маршрут, который вы хотите
namespace AppBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Routing\RouterInterface;
use AppBundle\Services\User\LoginFormService;
class LoginFormAuthenticator extends AbstractGuardAuthenticator {
private $router;
private $templatingEngine;
private $passwordEncoder;
private $csrfTokenManager;
private $loginService;
protected $auth_error_csrf = 'Invalid CSRF token!!!';
protected $auth_error_message = 'Invalid credentials!!!';
function __construct(
RouterInterface $router,
UserPasswordEncoderInterface $passwordEncoder,
CsrfTokenManagerInterface $csrfTokenManager,
LoginFormService $loginService) {
$this -> router = $router;
$this -> passwordEncoder = $passwordEncoder;
$this -> csrfTokenManager = $csrfTokenManager;
$this -> loginService = $loginService;
}
/* Methods */
protected function loginResponse(Request $request, $forbidden = False) {
// The javascript library must set the 'X-Requested-With' header
// xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
if ( $request -> isXmlHttpRequest() ) {
$response = new JsonResponse([
'error' => 1,
'html' => $this -> loginService -> getHtml(True)
]);
} else {
$html = $this -> loginService -> getHtml();
$response = new Response($html);
}
if ($forbidden) {
$response -> setStatusCode(Response::HTTP_FORBIDDEN);
}
return $response;
}
/* AbstractGuardAuthenticator methods */
public function supports(Request $request) {
return $request -> attributes -> get('_route') === 'security_login' && $request -> isMethod('POST');
}
public function getCredentials(Request $request) {
// Add csrf protection
$csrfData = $request -> request -> get('_csrf_token');
$csrfToken = new CsrfToken(LoginFormService::CSRF_TOKEN, $csrfData);
if ( !$this -> csrfTokenManager -> isTokenValid($csrfToken) ) {
throw new InvalidCsrfTokenException( $this -> auth_error_csrf );
}
return array(
'username' => $request -> request -> get('_username'),
'password' => $request -> request -> get('_password'),
);
}
public function getUser($credentials, UserProviderInterface $userProvider) {
$username = $credentials['username'];
try {
return $userProvider -> loadUserByUsername($username);
} catch (UsernameNotFoundException $e) {
throw new CustomUserMessageAuthenticationException( $this -> auth_error_message );
}
return null;
}
public function checkCredentials($credentials, UserInterface $user) {
$is_valid_password = $this -> passwordEncoder -> isPasswordValid($user, $credentials['password']);
if ( !$is_valid_password ) {
throw new CustomUserMessageAuthenticationException( $this -> auth_error_message );
return;
}
return True;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $authException) {
$session = $request -> getSession();
$session -> set(Security::AUTHENTICATION_ERROR, $authException);
$session -> set(Security::LAST_USERNAME, $request -> request -> get('_username'));
// Shows the login form instead of the page content
return $this -> loginResponse($request, True);
// If you want redirect make sure the line below is used
// return new RedirectResponse($this -> router -> generate('security_login'));
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) {
if ( $request -> isXmlHttpRequest() ) {
return new JsonResponse([
'success' => 1,
'message' => 'Authentication success!'
]);
}
return new RedirectResponse($this -> router -> generate('project_homepage_route'));
}
public function start(Request $request, AuthenticationException $authException = null) {
// Shows the login form instead of the page content
return $this -> loginResponse($request);
// If you want redirect make sure the line below is used
// return new RedirectResponse($this -> router -> generate('security_login'));
}
public function supportsRememberMe() {
return false;
}
}