Symfony Securityモジュールのドキュメントを見て、わかりました。
ユーザー名がユニークであるという事実を背景にSymfonyが完全に構築されているため、追加のフィールド(displayname)をUserモデルに追加しました。与えられたユーザー名を持つ最初のユーザーを常に取得しますが、これは私たちが望むものではありません。
私たちは独自のGuard Authentication Systemを作成し始めましたが、調整が必要でしたが、これはかなり簡単でした。 これはうまくいっていましたが、組み込みのUsernamePasswordFormAuthenticationListenerで問題が発生しましたが、このリスナーはまだログインフォームからdisplaynameを取得していました。実際には、Symfonyがどのユーザを使うかを知るために、一意のユーザ名が必要です。
標準のリスナーを拡張したカスタムリスナーを作成し、ユーザー名がログインフォームから取得されず、ユーザートークンから取得されていることを確認しました。
私たちの流れは次のようになります。ユーザーがユーザー名(実際に彼の表示名)とパスワードを入力すると、システムはその表示名を持つすべてのユーザーを取得します。次に、これらのユーザーをループして、誰かがそのパスワードを持っているかどうかを確認します。その場合は、ユーザーを認証します。 ユーザが作成すると、管理者が表示名を入力すると、システムはこれをユーザ名として自動的に増やします。 (admin_1、admin_2、...)。
私は@keroが真実だと言っているのか監視しなければなりませんが、Bcryptでは "123"のような単純なパスワードでもユーザーごとに違うハッシュになります。
残っているのは、表示名と電子メールのの組み合わせがのUniqueConstraintを持つことだけです。 orm.xmlとフォームでこれがどのように達成できるのか分かっている人は、ありがとう。
http://symfony.com/doc/current/security/guard_authentication.html
カスタムガード認証
class Authenticator extends AbstractGuardAuthenticator
{
private $encoderFactory;
private $userRepository;
private $tokenStorage;
private $router;
public function __construct(EncoderFactoryInterface $encoderFactory, UserRepositoryInterface $userRepository, TokenStorageInterface $tokenStorage, Router $router)
{
$this->encoderFactory = $encoderFactory;
$this->userRepository = $userRepository;
$this->tokenStorage = $tokenStorage;
$this->router = $router;
}
/**
* Called on every request. Return whatever credentials you want,
* or null to stop authentication.
*/
public function getCredentials(Request $request)
{
$encoder = $this->encoderFactory->getEncoder(new User());
$displayname = $request->request->get('_username');
$password = $request->request->get('_password');
$users = $this->userRepository->findByDisplayname($displayname);
if ($users !== []) {
foreach ($users as $user) {
if ($encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
return ['username' => $user->getUsername(), 'password' => $user->getPassword()];
}
}
} else {
if ($this->tokenStorage->getToken() !== null) {
$user = $this->tokenStorage->getToken()->getUser();
return ['username' => $user->getUsername(), 'password' => $user->getPassword()];
}
}
return null;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
if ($credentials !== null) {
return $userProvider->loadUserByUsername($credentials["username"]);
}
return null;
}
public function checkCredentials($credentials, UserInterface $user)
{
if ($user !== null) {
return true;
} else {
return false;
}
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$exclusions = ['/login'];
if (!in_array($request->getPathInfo(), $exclusions)) {
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
throw $exception;
}
}
/**
* Called when authentication is needed, but it's not sent
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$data = array(
// you might translate this message
'message' => 'Authentication Required'
);
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
public function supportsRememberMe()
{
return false;
}
}
カスタムリスナー
class CustomAuthListener extends UsernamePasswordFormAuthenticationListener
{
private $csrfTokenManager;
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfTokenManagerInterface $csrfTokenManager = null)
{
parent::__construct($tokenStorage, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge(array(
'username_parameter' => '_username',
'password_parameter' => '_password',
'csrf_parameter' => '_csrf_token',
'csrf_token_id' => 'authenticate',
'post_only' => true,
), $options), $logger, $dispatcher);
$this->csrfTokenManager = $csrfTokenManager;
$this->tokenStorage = $tokenStorage;
}
/**
* {@inheritdoc}
*/
protected function attemptAuthentication(Request $request)
{
if ($user = $this->tokenStorage->getToken() !== null) {
$user = $this->tokenStorage->getToken()->getUser();
$username = $user->getUsername();
if ($this->options['post_only']) {
$password = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']);
} else {
$password = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']);
}
if (strlen($username) > Security::MAX_USERNAME_LENGTH) {
throw new BadCredentialsException('Invalid username.');
}
$request->getSession()->set(Security::LAST_USERNAME, $username);
return $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $password, $this->providerKey));
} else {
return null;
}
}
}
リスナーサービス
<service id="security.authentication.listener.form" class="Your\Path\To\CustomAuthListener" parent="security.authentication.listener.abstract" abstract="true" />
IMOこれを達成するためにFOSUserBundleを使用しないでください。独自のUserBundleを作成する必要があります。 – Shady
私たちは現在、独自のGuardを実装しようとしていますが、FOSUserBundleとうまく連携しているように見えます。ソース:http://symfony.com/doc/current/security/guard_authentication.html –
同じユーザー名を持つ2人のユーザーが同じパスワードを共有している場合は、 – kero