Skip to content

Commit 8bb6c39

Browse files
dunglasfabpot
authored andcommitted
[Security] Add a JSON authentication listener
1 parent 199035b commit 8bb6c39

File tree

1 file changed

+154
-0
lines changed

1 file changed

+154
-0
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\Firewall;
13+
14+
use Psr\Log\LoggerInterface;
15+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
16+
use Symfony\Component\HttpFoundation\Request;
17+
use Symfony\Component\HttpFoundation\Response;
18+
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
19+
use Symfony\Component\PropertyAccess\Exception\AccessException;
20+
use Symfony\Component\PropertyAccess\PropertyAccess;
21+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
22+
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
23+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
24+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
25+
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
26+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
27+
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
28+
use Symfony\Component\Security\Core\Security;
29+
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
30+
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
31+
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
32+
use Symfony\Component\Security\Http\SecurityEvents;
33+
34+
/**
35+
* UsernamePasswordJsonAuthenticationListener is a stateless implementation of
36+
* an authentication via a JSON document composed of a username and a password.
37+
*
38+
* @author Kévin Dunglas <dunglas@gmail.com>
39+
*/
40+
class UsernamePasswordJsonAuthenticationListener implements ListenerInterface
41+
{
42+
private $tokenStorage;
43+
private $authenticationManager;
44+
private $providerKey;
45+
private $successHandler;
46+
private $failureHandler;
47+
private $options;
48+
private $logger;
49+
private $eventDispatcher;
50+
private $propertyAccessor;
51+
52+
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $eventDispatcher = null, PropertyAccessorInterface $propertyAccessor = null)
53+
{
54+
$this->tokenStorage = $tokenStorage;
55+
$this->authenticationManager = $authenticationManager;
56+
$this->providerKey = $providerKey;
57+
$this->successHandler = $successHandler;
58+
$this->failureHandler = $failureHandler;
59+
$this->logger = $logger;
60+
$this->eventDispatcher = $eventDispatcher;
61+
$this->options = array_merge(array('username_path' => 'username', 'password_path' => 'password'), $options);
62+
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
63+
}
64+
65+
/**
66+
* {@inheritdoc}
67+
*/
68+
public function handle(GetResponseEvent $event)
69+
{
70+
$request = $event->getRequest();
71+
$data = json_decode($request->getContent());
72+
73+
if (!$data instanceof \stdClass) {
74+
throw new BadCredentialsException('Invalid JSON.');
75+
}
76+
77+
try {
78+
$username = $this->propertyAccessor->getValue($data, $this->options['username_path']);
79+
} catch (AccessException $e) {
80+
throw new BadCredentialsException(sprintf('The key "%s" must be provided.', $this->options['username_path']));
81+
}
82+
83+
try {
84+
$password = $this->propertyAccessor->getValue($data, $this->options['password_path']);
85+
} catch (AccessException $e) {
86+
throw new BadCredentialsException(sprintf('The key "%s" must be provided.', $this->options['password_path']));
87+
}
88+
89+
if (!is_string($username)) {
90+
throw new BadCredentialsException(sprintf('The key "%s" must be a string.', $this->options['username_path']));
91+
}
92+
93+
if (strlen($username) > Security::MAX_USERNAME_LENGTH) {
94+
throw new BadCredentialsException('Invalid username.');
95+
}
96+
97+
if (!is_string($password)) {
98+
throw new BadCredentialsException(sprintf('The key "%s" must be a string.', $this->options['password_path']));
99+
}
100+
101+
try {
102+
$token = new UsernamePasswordToken($username, $password, $this->providerKey);
103+
104+
$this->authenticationManager->authenticate($token);
105+
$response = $this->onSuccess($request, $token);
106+
} catch (AuthenticationException $e) {
107+
$response = $this->onFailure($request, $e);
108+
}
109+
110+
$event->setResponse($response);
111+
}
112+
113+
private function onSuccess(Request $request, TokenInterface $token)
114+
{
115+
if (null !== $this->logger) {
116+
$this->logger->info('User has been authenticated successfully.', array('username' => $token->getUsername()));
117+
}
118+
119+
$this->tokenStorage->setToken($token);
120+
121+
if (null !== $this->eventDispatcher) {
122+
$loginEvent = new InteractiveLoginEvent($request, $token);
123+
$this->eventDispatcher->dispatch(SecurityEvents::INTERACTIVE_LOGIN, $loginEvent);
124+
}
125+
126+
$response = $this->successHandler->onAuthenticationSuccess($request, $token);
127+
128+
if (!$response instanceof Response) {
129+
throw new \RuntimeException('Authentication Success Handler did not return a Response.');
130+
}
131+
132+
return $response;
133+
}
134+
135+
private function onFailure(Request $request, AuthenticationException $failed)
136+
{
137+
if (null !== $this->logger) {
138+
$this->logger->info('Authentication request failed.', array('exception' => $failed));
139+
}
140+
141+
$token = $this->tokenStorage->getToken();
142+
if ($token instanceof UsernamePasswordToken && $this->providerKey === $token->getProviderKey()) {
143+
$this->tokenStorage->setToken(null);
144+
}
145+
146+
$response = $this->failureHandler->onAuthenticationFailure($request, $failed);
147+
148+
if (!$response instanceof Response) {
149+
throw new \RuntimeException('Authentication Failure Handler did not return a Response.');
150+
}
151+
152+
return $response;
153+
}
154+
}

0 commit comments

Comments
 (0)