Symfony 3.3基于REST接口的登录表单开发问题求助
Hey there! Let's sort out your Symfony + REST login flow step by step. I see a few key issues in your current code, plus we need to fix how you handle the REST response and integrate it properly with Symfony's Security system.
First: Clean Up Your Existing Code
Let's start with obvious fixes and bad practices:
- Remove the
loginCheckAction: Symfony's Security component automatically handles the/login_checkroute when configured correctly—your custom method here is unnecessary and will break the flow. - Fix form field names: Make sure your login form uses Symfony's default field names so the Security component can pick up credentials:
<form class="form-signin" action="{{ path('app_user_login_check') }}" method="POST"> <input type="text" name="_username" class="form-control" placeholder="Username" required autofocus> <input type="password" name="_password" class="form-control" placeholder="Password" required> <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button> </form> - Fix typos in
WebserviceUserProvider: Correct__contsructto__constructand remove the unused$userproperty (you don't need to inject a user here).
Second: Understand Symfony's Security Flow (Critical Fix!)
You're mixing up two core Security components:
UserProvider: Loads existing user data (by username)AuthenticationProvider: Validates user credentials (username + password)
Your REST login call should live in a custom AuthenticationProvider, not the UserProvider. Here's how to build this properly:
Step 1: Create a Custom Authentication Token
This holds the user's credentials during the authentication process:
// src/AppBundle/Security/Token/WebserviceAuthToken.php namespace AppBundle\Security\Token; use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; class WebserviceAuthToken extends AbstractToken { private $password; public function __construct($username, $password, array $roles = []) { parent::__construct($roles); $this->setUser($username); $this->password = $password; $this->setAuthenticated(count($roles) > 0); } public function getPassword() { return $this->password; } public function getCredentials() { return ''; // Don't store credentials after authentication } }
Step 2: Build the Custom AuthenticationProvider
This is where you'll call your REST API to validate credentials and fetch user data:
// src/AppBundle/Security/Authentication/WebserviceAuthProvider.php namespace AppBundle\Security\Authentication; use AppBundle\Security\User\WebserviceUser; use AppBundle\Security\Token\WebserviceAuthToken; use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Unirest; class WebserviceAuthProvider implements AuthenticationProviderInterface { public function authenticate(TokenInterface $token) { $username = $token->getUser(); $password = $token->getPassword(); // Call your REST login endpoint $headers = ['Accept' => 'application/json']; $payload = ['user' => $username, 'password' => $password]; try { $response = Unirest\Request::post('http://127.0.0.10:8888/login', $headers, $payload); } catch (\Exception $e) { // Handle API connection failures (timeout, unreachable) throw new AuthenticationException('Could not connect to authentication service.'); } // Check if the API returned a successful response if ($response->code !== 200) { throw new BadCredentialsException('Invalid username or password.'); } // Parse JSON response into an associative array $userData = json_decode($response->raw_body, true); if (empty($userData) || empty($userData['ldap'])) { throw new AuthenticationException('Invalid response from authentication service.'); } // Map REST data to your WebserviceUser (ensure this class has all custom fields) $user = new WebserviceUser( $userData['ldap']['document'], '', // Don't store passwords locally '', // Salt (if your API uses it, else leave empty) $this->mapRoles($userData['roles'] ?? []), // Convert REST roles to Symfony roles $userData['ldap']['document'], // 证件号 $userData['ldap']['full_name'], // 姓名 $userData['ldap']['userLdap'], $userData['ldap']['userEpersonal'], $userData['ldap']['mail'], // 邮箱 $userData['ldap']['position'] // 职位 ); // Create an authenticated token for the user $authenticatedToken = new WebserviceAuthToken($user->getUsername(), '', $user->getRoles()); $authenticatedToken->setUser($user); $authenticatedToken->setAuthenticated(true); return $authenticatedToken; } // Convert REST roles to Symfony's ROLE_* format private function mapRoles(array $restRoles): array { return array_map(function ($role) { return 'ROLE_' . strtoupper($role); }, $restRoles); } public function supports(TokenInterface $token) { return $token instanceof WebserviceAuthToken; } }
Step 3: Update Your WebserviceUserProvider
Now this class only handles loading user data (use a separate API endpoint if available):
// src/AppBundle/Security/User/WebserviceUserProvider.php namespace AppBundle\Security\User; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Unirest; class WebserviceUserProvider implements UserProviderInterface { public function loadUserByUsername($username) { // Fetch user data by username (use your API's user lookup endpoint) $headers = ['Accept' => 'application/json']; $response = Unirest\Request::get("http://127.0.0.10:8888/users/{$username}", $headers); if ($response->code !== 200) { throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username)); } $userData = json_decode($response->raw_body, true); if (empty($userData) || empty($userData['ldap'])) { throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username)); } return new WebserviceUser( $userData['ldap']['document'], '', '', $this->mapRoles($userData['roles'] ?? []), $userData['ldap']['document'], $userData['ldap']['full_name'], $userData['ldap']['userLdap'], $userData['ldap']['userEpersonal'], $userData['ldap']['mail'], $userData['ldap']['position'] ); } private function mapRoles(array $restRoles): array { return array_map(function ($role) { return 'ROLE_' . strtoupper($role); }, $restRoles); } public function refreshUser(UserInterface $user) { if (!$user instanceof WebserviceUser) { throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); } return $this->loadUserByUsername($user->getUsername()); } public function supportsClass($class) { return WebserviceUser::class === $class; } }
Step 4: Add a Guard Authenticator
This bridges the form submission to your custom authentication flow:
// src/AppBundle/Security/Guard/WebserviceAuthenticator.php namespace AppBundle\Security\Guard; use AppBundle\Security\Token\WebserviceAuthToken; 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\Core\Security; use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; class WebserviceAuthenticator extends AbstractGuardAuthenticator { private $router; public function __construct(RouterInterface $router) { $this->router = $router; } public function getCredentials(Request $request) { // Only handle the login check POST request if ($request->getPathInfo() !== '/login_check' || !$request->isMethod('POST')) { return null; } return [ 'username' => $request->request->get('_username'), 'password' => $request->request->get('_password'), ]; } public function getUser($credentials, \Symfony\Component\Security\Core\User\UserProviderInterface $userProvider) { // We'll handle user loading in the AuthenticationProvider return null; } public function checkCredentials($credentials, \Symfony\Component\Security\Core\User\UserInterface $user) { // Credential validation happens in the AuthenticationProvider return true; } public function createAuthenticatedToken(\Symfony\Component\Security\Core\User\UserInterface $user, $providerKey) { return new WebserviceAuthToken($user->getUsername(), '', $user->getRoles()); } public function supportsToken(TokenInterface $token, $providerKey) { return $token instanceof WebserviceAuthToken; } public function start(Request $request, AuthenticationException $authException = null) { // Redirect to login page if unauthenticated return new RedirectResponse($this->router->generate('app_user_login')); } public function onAuthenticationFailure(Request $request, AuthenticationException $exception) { // Store error message and redirect back to login $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception); return new RedirectResponse($this->router->generate('app_user_login')); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) { // Redirect to homepage after successful login return new RedirectResponse($this->router->generate('homepage')); } public function supports(Request $request) { return $request->getPathInfo() === '/login_check' && $request->isMethod('POST'); } }
Step 5: Update Configuration
Update your services.yml to register the new components:
services: webservice_user_provider: class: AppBundle\Security\User\WebserviceUserProvider AppBundle\Security\Authentication\WebserviceAuthProvider: autowire: true AppBundle\Security\Guard\WebserviceAuthenticator: autowire: true arguments: $router: '@router'
And update security.yml to use the guard authenticator:
security: providers: webservice: id: webservice_user_provider firewalls: main: pattern: ^/ anonymous: true logout: true guard: authenticators: - AppBundle\Security\Guard\WebserviceAuthenticator access_control: # Example: Restrict admin routes to ROLE_ADMIN - { path: ^/admin, roles: ROLE_ADMIN } # Require login for all other routes - { path: ^/, roles: ROLE_USER }
Third: Handle REST Responses Properly
We added robust handling for your API interactions:
- Connection Error Handling: Catch exceptions for unreachable APIs or timeouts
- Status Code Validation: Only process responses with a 200 OK status
- JSON Parsing: Convert the raw JSON response to an array for easy data access
- Data Sanity Checks: Ensure the response contains the expected user data before proceeding
Fourth: Access User Data & Control Permissions
Once logged in:
- Access User Data: In controllers, use
$this->getUser()to fetch theWebserviceUserobject, then call getters like$this->getUser()->getFullName()or$this->getUser()->getMail(). In Twig, use{{ app.user.fullName }}. - Control Access:
- Use
access_controlrules insecurity.ymlto restrict routes - In controllers:
$this->denyAccessUnlessGranted('ROLE_ADMIN') - In Twig:
{% if is_granted('ROLE_ADMIN') %}to show/hide elements
- Use
内容的提问来源于stack exchange,提问作者WltrRpo

