Я использую Datatables.net в своем приложении Symfony4. Он отправляет ajax-запрос с кучей переменных POST в контроллер: UserController-> searchApi (). Вызываемый URL-адрес https://127.0.0.1:8000/users-search
. Однако запрашиваемая страница перенаправляется на страницу / login, потому что пользователь Symfony является анонимным.
Итак, для целей отладки я отключил перенаправление и открыл целевую страницу через Firefox devTools. Затем я могу получить доступ к панели инструментов отладки, предоставив полезную информацию:
- Тест 1: меня перенаправляют, когда я открываю запрос POST на
https://127.0.0.1:8000/users-search
, отправленный Datatables. - Тест 2: я не перенаправляется, и контроллер правильно вызывается при достижении страницы с помощью GET
- Тест 3: я не перенаправляется, и контроллер правильно вызывается, когда я захожу на страницу по POST при использовании формы на странице, вызываемой через GET
- URL-адрес строго идентичен в этих 3 случаях использования
- «Основной» брандмауэр используется на перенаправленной странице, согласно панели отладки Symfony. Пользователь по-прежнему анонимный.
- Файл cookie PHPSESSID присутствует и точен на перенаправленной странице, а также на странице, к которой осуществляется доступ.
Похоже, что значения POSTed от Datatables являются единственным различием между тестами 1 и 3. Поэтому я использовал Firefox devTools для редактирования запроса, отправленного со страницы источника (с Datatables на нем), и удалилпараметры, а затем повторно отправил запрос. Ну, это ломает сервер Symfony без предупреждения, и я должен перезапустить его. Странно, но я думаю, что не связано.
Вот security.yml:
security:
encoders:
App\Entity\User:
algorithm: auto
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
form_login:
csrf_token_generator: security.csrf.token_manager
logout: true
anonymous: true
guard:
authenticators:
- App\Security\LoginFormAuthenticator
remember_me:
secret: '%kernel.secret%'
lifetime: 3024000 # 5 weeks
path: /
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/forgottenPassword$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetPassword/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: IS_AUTHENTICATED_REMEMBERED }
Соответствующие части контроллеров
/**
* @Route("/users", name="app_user_search")
*/
public function search(UserService $userService)
{
$this->denyAccessUnlessGranted('searchUsers');
return $this->render('user/search.html.twig', []);
}
/**
* @Route("/users-search", name="app_user_search_api")
*/
public function searchApi(Request $request, EntityManagerInterface $em)
{
echo '<form method="post"><input type="submit"></form>';
die();
}
И запрос от Firefox Devtool
Request URL:https://127.0.0.1:8000/users-search
Request Method:POST
Remote Address:127.0.0.1:8000
Status Code:
200
Version:HTTP/2.0
Referrer Policy:no-referrer-when-downgrade
С параметрами (довольно случайно)
draw=1&columns%5B0%5D%5Bdata%5D=0&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=1&columns%5B1%5D%5Bname%5D=&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=2&columns%5B2%5D%5Bname%5D=&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B3%5D%5Bdata%5D=3&columns%5B3%5D%5Bname%5D=&columns%5B3%5D%5Bsearchable%5D=true&columns%5B3%5D%5Borderable%5D=false&columns%5B3%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B3%5D%5Bsearch%5D%5Bregex%5D=false&start=0&length=25&search%5Bvalue%5D=&search%5Bregex%5D=false
Редактировать: избиратель
<?php
namespace App\Security\Voter;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
class UserVoter extends Voter
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
protected function supports($attribute, $subject)
{
// By entity
if($subject instanceof \App\Entity\User)
return in_array($attribute, [
'read',
'update',
'delete',
]);
// Not entity-related
return in_array($attribute, [
'searchUsers',
'createUser',
]);
}
/**
* @param string $attribute
* @param User $user
* @param TokenInterface $token
* @return bool|mixed
*/
protected function voteOnAttribute($attribute, $user, TokenInterface $token)
{
$currentUser = $this->security->getUser();
// if the user is anonymous, do not grant access
if (!$currentUser instanceof UserInterface)
return false;
switch ($attribute) {
case 'createUser':
case 'searchUsers':
return $this->security->isGranted("ROLE_ADMIN")
;
case 'read':
return $user->getCreatedBy() === $currentUser
|| $user === $currentUser
;
case 'update':
return $this->security->isGranted("ROLE_ADMIN")
|| $user === $currentUser
;
case 'delete':
return $this->security->isGranted("ROLE_ADMIN")
&& $user !== $currentUser
;
}
return false;
}
}