PHP项目Symfony Authenticator多种

wen PHP项目 1

本文目录导读:

PHP项目Symfony Authenticator多种

  1. 基础认证表单登录 (Form Login)
  2. API Token 认证
  3. JWT 认证 (LexikJWTAuthenticationBundle)
  4. 双因素认证 (Two-Factor Authentication)
  5. OAuth / Social Login 认证
  6. LDAP 认证
  7. 自定义认证 Provider
  8. 组合认证 (Multiple Authenticators)
  9. 配置示例
  10. 自定义访问控制

我来详细介绍一下 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 提供了多种灵活的认证方式:

  1. 表单登录 - 基本的用户名/密码认证
  2. API Token - 适用于 REST API
  3. JWT - 无状态认证,适合微服务
  4. 双因素认证 - 增强安全性
  5. OAuth/社交登录 - 第三方登录
  6. LDAP - 企业级集成
  7. 自定义 Provider - 灵活的认证逻辑
  8. 组合认证 - 支持多认证方式并存

选择哪种认证方式取决于你的应用需求:

  • Web 应用:表单登录 + 记住我
  • API:JWT 或 API Token
  • 企业应用:LDAP + 双因素认证
  • 社交平台:OAuth

需要更具体的实现细节吗?

抱歉,评论功能暂时关闭!