本文目录导读:

- 基础认证表单登录 (Form Login)
- API Token 认证
- JWT 认证 (LexikJWTAuthenticationBundle)
- 双因素认证 (Two-Factor Authentication)
- OAuth / Social Login 认证
- LDAP 认证
- 自定义认证 Provider
- 组合认证 (Multiple Authenticators)
- 配置示例
- 自定义访问控制
我来详细介绍一下 Symfony Authenticator 的多种实现方式。
基础认证表单登录 (Form Login)
// src/Security/LoginFormAuthenticator.php
namespace App\Security;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
public function __construct(
private UrlGeneratorInterface $urlGenerator
) {
}
public function authenticate(Request $request): Passport
{
$email = $request->request->get('email', '');
$request->getSession()->set(Security::LAST_USERNAME, $email);
return new Passport(
new UserBadge($email),
new PasswordCredentials($request->request->get('password', '')),
[
new CsrfTokenBadge('authenticate', $request->request->get('_csrf_token')),
new RememberMeBadge(),
]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('app_home'));
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
API Token 认证
// src/Security/ApiTokenAuthenticator.php
namespace App\Security;
use App\Repository\ApiTokenRepository;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class ApiTokenAuthenticator extends AbstractAuthenticator
{
public function __construct(
private ApiTokenRepository $apiTokenRepository
) {
}
public function supports(Request $request): ?bool
{
return $request->headers->has('X-AUTH-TOKEN');
}
public function authenticate(Request $request): Passport
{
$apiToken = $request->headers->get('X-AUTH-TOKEN');
if (null === $apiToken) {
throw new CustomUserMessageAuthenticationException('No API token provided');
}
$apiToken = $this->apiTokenRepository->findOneBy(['token' => $apiToken]);
if (null === $apiToken || $apiToken->isExpired()) {
throw new CustomUserMessageAuthenticationException('Invalid or expired API token');
}
return new SelfValidatingPassport(
new UserBadge($apiToken->getUser()->getUserIdentifier())
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return null; // 继续处理请求
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
$data = [
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
}
JWT 认证 (LexikJWTAuthenticationBundle)
// src/Security/JWTAuthenticator.php
namespace App\Security;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator\JWTAuthenticator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class JWTAuthenticator extends AbstractAuthenticator
{
public function supports(Request $request): ?bool
{
return $request->headers->has('Authorization')
&& str_starts_with($request->headers->get('Authorization'), 'Bearer ');
}
public function authenticate(Request $request): Passport
{
$token = substr($request->headers->get('Authorization'), 7);
// 解析和验证 JWT token
$jwtToken = $this->jwtManager->decode($token);
if (null === $jwtToken) {
throw new CustomUserMessageAuthenticationException('Invalid JWT token');
}
return new SelfValidatingPassport(
new UserBadge($jwtToken['username'], function ($userIdentifier) {
return $this->userProvider->loadUserByIdentifier($userIdentifier);
})
);
}
}
双因素认证 (Two-Factor Authentication)
// src/Security/TwoFactorAuthenticator.php
namespace App\Security;
use Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorToken;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
class TwoFactorAuthenticator extends AbstractAuthenticator
{
public function authenticate(Request $request): Passport
{
$username = $request->request->get('username');
$password = $request->request->get('password');
$twoFactorCode = $request->request->get('2fa_code');
return new Passport(
new UserBadge($username),
new PasswordCredentials($password),
[
new TwoFactorCodeBadge($twoFactorCode),
new RememberMeBadge(),
]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
if ($token instanceof TwoFactorToken) {
// 需要完成双因素认证
return new RedirectResponse('/2fa');
}
return new RedirectResponse('/dashboard');
}
}
OAuth / Social Login 认证
// src/Security/OAuthAuthenticator.php
namespace App\Security;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
class GoogleAuthenticator extends AbstractAuthenticator
{
public function supports(Request $request): ?bool
{
return $request->attributes->get('_route') === 'connect_google_check';
}
public function authenticate(Request $request): Passport
{
$client = $this->clientRegistry->getClient('google');
$accessToken = $this->fetchAccessToken($client);
return new SelfValidatingPassport(
new UserBadge($accessToken->getToken(), function () use ($accessToken, $client) {
$googleUser = $client->fetchUserFromToken($accessToken);
// 查找或创建用户
$existingUser = $this->userRepository->findOneBy([
'googleId' => $googleUser->getId()
]);
if ($existingUser) {
return $existingUser;
}
// 创建新用户
$user = new User();
$user->setEmail($googleUser->getEmail());
$user->setGoogleId($googleUser->getId());
$this->entityManager->persist($user);
$this->entityManager->flush();
return $user;
})
);
}
}
LDAP 认证
// src/Security/LdapAuthenticator.php
namespace App\Security;
use Symfony\Component\Ldap\Ldap;
use Symfony\Component\Ldap\Security\LdapBadge;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
class LdapAuthenticator extends AbstractLoginFormAuthenticator
{
public function __construct(
private Ldap $ldap,
private string $ldapDn = 'ou=users,dc=example,dc=com',
private string $ldapSearchDn = 'cn=admin,dc=example,dc=com',
private string $ldapSearchPassword = 'password'
) {
}
public function authenticate(Request $request): Passport
{
$username = $request->request->get('username', '');
$password = $request->request->get('password', '');
return new Passport(
new UserBadge($username, function ($userIdentifier) {
// 从 LDAP 获取用户
$user = $this->userProvider->loadUserByIdentifier($userIdentifier);
return $user;
}),
new PasswordCredentials($password),
[
new LdapBadge(
$this->ldap,
$this->ldapDn,
$this->ldapSearchDn,
$this->ldapSearchPassword
),
]
);
}
}
自定义认证 Provider
// src/Security/CustomUserProvider.php
namespace App\Security;
use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class CustomUserProvider implements UserProviderInterface, PasswordUpgraderInterface
{
public function __construct(
private UserRepository $userRepository
) {
}
public function loadUserByIdentifier(string $identifier): UserInterface
{
// 支持通过邮箱或用户名查找
$user = $this->userRepository->findOneBy(['email' => $identifier])
?? $this->userRepository->findOneBy(['username' => $identifier]);
if (!$user) {
throw new UserNotFoundException(sprintf('User "%s" not found.', $identifier));
}
return $user;
}
public function refreshUser(UserInterface $user): UserInterface
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
}
return $this->loadUserByIdentifier($user->getUserIdentifier());
}
public function supportsClass(string $class): bool
{
return User::class === $class || is_subclass_of($class, User::class);
}
public function upgradePassword(UserInterface $user, string $newHashedPassword): void
{
$user->setPassword($newHashedPassword);
$this->entityManager->persist($user);
$this->entityManager->flush();
}
}
组合认证 (Multiple Authenticators)
# config/packages/security.yaml
security:
firewalls:
main:
pattern: ^/
stateless: false
# 组合多个认证器
entry_point: App\Security\LoginFormAuthenticator
custom_authenticators:
- App\Security\LoginFormAuthenticator
- App\Security\ApiTokenAuthenticator
- App\Security\JWTAuthenticator
# 设置认证器的优先级
# 第一个匹配的会被使用
json_login:
check_path: /api/login
form_login:
login_path: /login
check_path: /login_check
logout:
path: /logout
target: /
配置示例
# config/packages/security.yaml
security:
enable_authenticator_manager: true
password_hashers:
App\Entity\User: '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
# 自定义认证器
custom_authenticators:
- App\Security\LoginFormAuthenticator
- App\Security\ApiTokenAuthenticator
# 登录配置
form_login:
login_path: app_login
check_path: app_login
enable_csrf: true
# 退出登录
logout:
path: app_logout
# 退出后跳转
target: app_login
# 记住我功能
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 1周
path: /
always_remember_me: true
# 访问控制
access_denied_handler: App\Security\AccessDeniedHandler
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/profile, roles: ROLE_USER }
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/api/public, roles: PUBLIC_ACCESS }
- { path: ^/api, roles: ROLE_API_USER }
自定义访问控制
// src/Security/Voter/PostVoter.php
namespace App\Security\Voter;
use App\Entity\Post;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
class PostVoter extends Voter
{
const VIEW = 'view';
const EDIT = 'edit';
const DELETE = 'delete';
protected function supports(string $attribute, $subject): bool
{
if (!in_array($attribute, [self::VIEW, self::EDIT, self::DELETE])) {
return false;
}
if (!$subject instanceof Post) {
return false;
}
return true;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return false;
}
/** @var Post $post */
$post = $subject;
switch ($attribute) {
case self::VIEW:
return $this->canView($post, $user);
case self::EDIT:
return $this->canEdit($post, $user);
case self::DELETE:
return $this->canDelete($post, $user);
}
return false;
}
private function canView(Post $post, UserInterface $user): bool
{
// 所有人都可以查看公开文章
if ($post->isPublished()) {
return true;
}
// 作者可以查看未发布的文章
return $user === $post->getAuthor();
}
private function canEdit(Post $post, UserInterface $user): bool
{
return $user === $post->getAuthor() || in_array('ROLE_ADMIN', $user->getRoles());
}
private function canDelete(Post $post, UserInterface $user): bool
{
return $user === $post->getAuthor();
}
}
Symfony 提供了多种灵活的认证方式:
- 表单登录 - 基本的用户名/密码认证
- API Token - 适用于 REST API
- JWT - 无状态认证,适合微服务
- 双因素认证 - 增强安全性
- OAuth/社交登录 - 第三方登录
- LDAP - 企业级集成
- 自定义 Provider - 灵活的认证逻辑
- 组合认证 - 支持多认证方式并存
选择哪种认证方式取决于你的应用需求:
- Web 应用:表单登录 + 记住我
- API:JWT 或 API Token
- 企业应用:LDAP + 双因素认证
- 社交平台:OAuth
需要更具体的实现细节吗?