我正在尝试在 Symfony 5.4 上实现与 Keycloak 的 OAuth 连接,当我显示应用程序的页面时,一切正常,我有 keycloak 登录页面,但在验证后,出现此错误:
Argument 1 passed to Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator::__construct() must implement interface Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface, instance of App\Security\KeycloakAuthenticator given, called in /var/www/oauth-symfony/vendor/symfony/security-http/Authenticator/Debug/TraceableAuthenticatorManagerListener.php on line 60
显然我尝试添加
implements AuthenticatorInterface
我不得不添加 authenticate() 和 createToken() 方法,但即使这样,实现仍然不起作用。
<?php
namespace App\Security;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Class KeycloakAuthenticator
*/
class KeycloakAuthenticator extends SocialAuthenticator
{
/**
* ClientRegistry: the OAuth client manager
* EntityManagerInterface: to read/write in database
* RouterInterface: read a route/URL
*/
private $clientRegistry;
private $em;
private $router;
public function __construct(
ClientRegistry $clientRegistry,
EntityManagerInterface $em,
RouterInterface $router
)
{
$this->clientRegistry = $clientRegistry;
$this->em = $em;
$this->router = $router;
}
public function start(Request $request, \Symfony\Component\Security\Core\Exception\AuthenticationException $authenticationException = null): RedirectResponse
{
return new RedirectResponse(
'/oauth/login', // might be the site, where users choose their oauth provider
Response::HTTP_TEMPORARY_REDIRECT
);
}
public function supports(Request $request): ?bool
{
return $request->attributes->get('_route') === 'oauth_check';
}
public function getCredentials(Request $request)
{
return $this->fetchAccessToken($this->getKeycloakClient());
}
public function getUser($credentials, \Symfony\Component\Security\Core\User\UserProviderInterface $userProvider)
{
$keycloakUser = $this->getKeycloakClient()->fetchUserFromToken($credentials);
//existing user ?
$existingUser = $this
->em
->getRepository(User::class)
->findOneBy(['keycloakId' => $keycloakUser->getId()]);
if ($existingUser) {
return $existingUser;
}
// if user exist but never connected with keycloak
$email = $keycloakUser->getEmail();
/** @var User $userInDatabase */
$userInDatabase = $this->em->getRepository(User::class)
->findOneBy(['email' => $email]);
if($userInDatabase) {
$userInDatabase->setKeycloakId($keycloakUser->getId());
$this->em->persist($userInDatabase);
$this->em->flush();
return $userInDatabase;
}
//user not exist in database
$user = new User();
$user->setKeycloakId($keycloakUser->getId());
$user->setEmail($keycloakUser->getEmail());
$user->setRoles(['ROLE_USER']);
$this->em->persist($user);
$this->em->flush();
return $user;
}
public function onAuthenticationFailure(Request $request, \Symfony\Component\Security\Core\Exception\AuthenticationException $exception)
{
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
return new Response($message, Response::HTTP_FORBIDDEN);
}
public function onAuthenticationSuccess(Request $request, \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token, string $providerKey)
{
// change "app_homepage" to some route in your app
$targetUrl = $this->router->generate('dashboard');
return new RedirectResponse($targetUrl);
}
/**
* @return \KnpU\OAuth2ClientBundle\Client\Provider\KeycloakClient
*/
private function getKeycloakClient()
{
return $this->clientRegistry->getClient('keycloak');
}
}
security:
enable_authenticator_manager: true
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
entry_point: form_login
form_login:
login_path: oauth_login
custom_authenticator: App\Security\KeycloakAuthenticator
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
- { path: ^/dashboard, roles: ROLE_USER }
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Annotation\Route;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Client\Provider\KeycloakClient;
class OAuthController extends AbstractController
{
/**
* @Route("/oauth/login", name="oauth_login")
*/
public function index(ClientRegistry $registry): RedirectResponse
{
/**@var KeycloakClient $client */
$client = $registry->getClient('keycloak');
return $client->redirect();
}
/**
* @Route("/oauth/callback", name="oauth_check")
*/
public function check(){}
}
您需要替换要通过 OAuth2Authenticator 扩展的类1。
在 onAuthenticationSuccess 和 onAuthenticationFailure 上添加输入“: ?Response”
并创建函数“authenticate”来返回 PassportInterface