Symfony 2.8 REST API:能否通过GuardAuthenticator复用用户会话认证?
Absolutely! You can leverage the existing user session from your FOSUserBundle-powered GUI to authenticate REST API calls via Ajax—no need for an explicit API token for those users. Here's a step-by-step approach to make this work with your GuardAuthenticator setup:
Option 1: Modify Your Existing Token GuardAuthenticator
The simplest way is to extend your existing stateless GuardAuthenticator to check for an authenticated user session first, before falling back to the API token.
1. Update the supports() Method
First, adjust your authenticator to recognize requests where a valid user session already exists:
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; public function supports(Request $request): bool { // Check if there's an already authenticated user in the session $existingSessionToken = $this->tokenStorage->getToken(); if ($existingSessionToken instanceof TokenInterface && $existingSessionToken->isAuthenticated() && !$existingSessionToken instanceof AnonymousToken) { return true; } // Fall back to checking for the API token header return $request->headers->has('X-AUTH-TOKEN'); }
2. Adjust getCredentials() to Handle Session Users
Modify this method to return the session user if available, instead of just the API token:
public function getCredentials(Request $request) { $existingSessionToken = $this->tokenStorage->getToken(); if ($existingSessionToken instanceof TokenInterface && $existingSessionToken->isAuthenticated() && !$existingSessionToken instanceof AnonymousToken) { // Return the session user as credentials return ['user' => $existingSessionToken->getUser()]; } // Original token retrieval logic return $request->headers->get('X-AUTH-TOKEN'); }
3. Update authenticate() to Support Both Session and Token Auth
Handle both credential types in the authentication step:
use Symfony\Component\Security\Http\Authenticator\Passport\UserPassport; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; public function authenticate(Request $request): PassportInterface { $credentials = $this->getCredentials($request); // Handle session-based authentication if (is_array($credentials) && isset($credentials['user'])) { $user = $credentials['user']; return new UserPassport( $user, null, // No password needed since user is already authenticated [new UserBadge($user->getUserIdentifier())] ); } // Original API token authentication logic $token = $credentials; if (empty($token)) { throw new AuthenticationException('No API token provided'); } $user = $this->userRepository->findOneBy(['apiToken' => $token]); if (!$user) { throw new AuthenticationException('Invalid API token'); } return new UserPassport( $user, null, [new UserBadge($user->getUserIdentifier())] ); }
Option 2: Add a Dedicated Session Authenticator (Cleaner Separation)
For better separation of concerns, create a separate authenticator to handle session-based API auth, then add it to your API firewall alongside your token authenticator.
1. Create a SessionAuthenticator
namespace App\Security; 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\UserPassport; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Core\Security; class SessionAuthenticator extends AbstractAuthenticator { private $security; public function __construct(Security $security) { $this->security = $security; } public function supports(Request $request): bool { // Only activate if there's an authenticated user in the session return $this->security->isGranted('IS_AUTHENTICATED_FULLY'); } public function getCredentials(Request $request) { return $this->security->getUser(); } public function authenticate(Request $request): PassportInterface { $user = $this->getCredentials($request); return new UserPassport( $user, null, [new UserBadge($user->getUserIdentifier())] ); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { // Let the request proceed normally return null; } public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { // Fall back to the next authenticator (token-based) return null; } }
2. Update Your Security Configuration
Add both authenticators to your API firewall, making sure to set stateless: false (since we need to access the session):
# config/packages/security.yaml security: firewalls: # Your existing GUI firewall with FOSUserBundle main: pattern: ^/ fos_user: ~ logout: ~ # API firewall with dual authentication api: pattern: ^/api stateless: false guard: authenticators: - App\Security\SessionAuthenticator - App\Security\TokenAuthenticator
Key Notes
- Browser Cookie Handling: By default, browsers automatically send session cookies for same-domain requests, so your Ajax calls from the GUI will work without extra setup. For cross-domain requests, add
withCredentials: trueto your Ajax configuration. - Stateless Compatibility: Even if you enable session support for the API firewall, external clients without a session will still use the token-based auth as usual.
- Security: Ensure your API routes are properly secured with roles, just like your GUI routes—this approach only handles authentication, not authorization.
内容的提问来源于stack exchange,提问作者micha

