Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on May 31, 2024. It is now read-only.
/securityPublic archive

Commit4a5dea2

Browse files
committed
feature #14673 New Guard Authentication System (e.g. putting the joy back into security) (weaverryan)
This PR was merged into the 2.8 branch.Discussion----------New Guard Authentication System (e.g. putting the joy back into security)| Q | A| ------------- | ---| Bug fix? | no| New feature? | yes| BC breaks? | no| Deprecations? | no| Tests pass? | yes| Fixed tickets | at least partially: #14300, #11158, #11451, #10035, #10463, #8606, probably more| License | MIT| Doc PR |symfony/symfony-docs#5265Hi guys!Though it got much easier in 2.4 with `pre_auth`, authentication is a pain in Symfony. This introduces a new authentication provider called guard, with one goal in mind: put everything you need for *any* authentication system into one spot.### How it worksWith guard, you can perform custom authentication just by implementing the [GuardAuthenticatorInterface](https://github.com/weaverryan/symfony/blob/guard/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php) and registering it as a service. It has methods for every part of a custom authentication flow I can think of.For a working example, seehttps://github.com/weaverryan/symfony-demo/tree/guard-auth. This uses 2 authenticators simultaneously, creating a system that handles [form login](https://github.com/weaverryan/symfony-demo/blob/guard-auth/src/AppBundle/Security/FormLoginAuthenticator.php) and [api token auth](https://github.com/weaverryan/symfony-demo/blob/guard-auth/src/AppBundle/Security/TokenAuthenticator.php) with a respectable amount of code. The [security.yml](https://github.com/weaverryan/symfony-demo/blob/guard-auth/app/config/security.yml) is also quite simple.This also supports "manual login" without jumping through hoops:https://github.com/weaverryan/symfony-demo/blob/guard-auth/src/AppBundle/Controller/SecurityController.php#L45I've also tested with "remember me" and "switch user" - no problems with either.I hope you like it :).### What's Needed1) **Other Use-Cases?**: Please think about the code and try it. What use-cases are we *not* covering? I want Guard to be simple, but cover the 99.9% use-cases.2) **Remember me** functionality cannot be triggered via manual login. That's true now, and it's not fixed, and it's tricky.### Deprecations?This is a new feature, so no deprecations. But, creating a login form with a guard authenticator is a whole heck of a lot easier to understand than `form_login` or even `simple_form`. In a perfect world, we'd either deprecate those or make them use "guard" internally so that we have just **one** way of performing authentication.Thanks!Commits-------a01ed35 Adding the necessary files so that Guard can be its own installable componentd763134 Removing unnecessary overridee353833 fabbotdd485f4 Adding a new exception and throwing it when the User changes302235e Fixing a bug where having an authentication failure would log you out.396a162 Tweaks thanks to Wouterc9d9430 Adding logging on this step and switching the order - not for any huge reason31f9cae Adding a base class to assist with form login authentication0501761 Allowing for other authenticators to be checked293c8a1 meaningless author and license changes81432f9 Adding missing factory registration7a94994 Thanks again fabbot!7de05be A few more changes thanks to@iltarffdbc66 Splitting the getting of the user and checking credentials into two steps6edb9e1 Tweaking docblock on interface thanks to@iltard693721 Adding periods at the end of exceptions, and changing one class name to LogicException thanks to@iltareb158cb Updating interface method per suggestion - makes sense to me, Request is redundantc73c32e Thanks fabbot!6c180c7 Adding an edge case - this should not happen anyways180e2c7 Properly handles "post auth" tokens that have become not authenticated873ed28 Renaming the tokens to be clear they are "post" and "pre" auth - also adding an interfacea0bceb4 adding Guard tests05af97c Initial commit (but after some polished work) of the new Guard authentication system330aa7f Improving phpdoc on AuthenticationEntryPointInterface so people that implement this understand it
2 parents8c2b94f +038c729 commit4a5dea2

20 files changed

+1587
-3
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
namespaceSymfony\Component\Security\Core\Exception;
13+
14+
/**
15+
* AuthenticationServiceException is thrown when an authenticated token becomes un-authentcated between requests.
16+
*
17+
* In practice, this is due to the User changing between requests (e.g. password changes),
18+
* causes the token to become un-authenticated.
19+
*
20+
* @author Ryan Weaver <ryan@knpuniversity.com>
21+
*/
22+
class AuthenticationExpiredExceptionextends AccountStatusException
23+
{
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
publicfunctiongetMessageKey()
28+
{
29+
return'Authentication expired because your account information has changed.';
30+
}
31+
}

‎Guard/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml

‎Guard/AbstractGuardAuthenticator.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
namespaceSymfony\Component\Security\Guard;
13+
14+
useSymfony\Component\Security\Core\User\UserInterface;
15+
useSymfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
16+
17+
/**
18+
* An optional base class that creates a PostAuthenticationGuardToken for you.
19+
*
20+
* @author Ryan Weaver <ryan@knpuniversity.com>
21+
*/
22+
abstractclass AbstractGuardAuthenticatorimplements GuardAuthenticatorInterface
23+
{
24+
/**
25+
* Shortcut to create a PostAuthenticationGuardToken for you, if you don't really
26+
* care about which authenticated token you're using.
27+
*
28+
* @param UserInterface $user
29+
* @param string $providerKey
30+
*
31+
* @return PostAuthenticationGuardToken
32+
*/
33+
publicfunctioncreateAuthenticatedToken(UserInterface$user,$providerKey)
34+
{
35+
returnnewPostAuthenticationGuardToken(
36+
$user,
37+
$providerKey,
38+
$user->getRoles()
39+
);
40+
}
41+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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+
namespaceSymfony\Component\Security\Guard\Authenticator;
13+
14+
useSymfony\Component\Security\Guard\AbstractGuardAuthenticator;
15+
useSymfony\Component\HttpFoundation\RedirectResponse;
16+
useSymfony\Component\HttpFoundation\Request;
17+
useSymfony\Component\Security\Core\Authentication\Token\TokenInterface;
18+
useSymfony\Component\Security\Core\Exception\AuthenticationException;
19+
useSymfony\Component\Security\Core\Security;
20+
21+
/**
22+
* A base class to make form login authentication easier!
23+
*
24+
* @author Ryan Weaver <ryan@knpuniversity.com>
25+
*/
26+
abstractclass AbstractFormLoginAuthenticatorextends AbstractGuardAuthenticator
27+
{
28+
/**
29+
* Return the URL to the login page.
30+
*
31+
* @return string
32+
*/
33+
abstractprotectedfunctiongetLoginUrl();
34+
35+
/**
36+
* The user will be redirected to the secure page they originally tried
37+
* to access. But if no such page exists (i.e. the user went to the
38+
* login page directly), this returns the URL the user should be redirected
39+
* to after logging in successfully (e.g. your homepage).
40+
*
41+
* @return string
42+
*/
43+
abstractprotectedfunctiongetDefaultSuccessRedirectUrl();
44+
45+
/**
46+
* Override to change what happens after a bad username/password is submitted.
47+
*
48+
* @param Request $request
49+
* @param AuthenticationException $exception
50+
*
51+
* @return RedirectResponse
52+
*/
53+
publicfunctiononAuthenticationFailure(Request$request,AuthenticationException$exception)
54+
{
55+
$request->getSession()->set(Security::AUTHENTICATION_ERROR,$exception);
56+
$url =$this->getLoginUrl();
57+
58+
returnnewRedirectResponse($url);
59+
}
60+
61+
/**
62+
* Override to change what happens after successful authentication.
63+
*
64+
* @param Request $request
65+
* @param TokenInterface $token
66+
* @param string $providerKey
67+
*
68+
* @return RedirectResponse
69+
*/
70+
publicfunctiononAuthenticationSuccess(Request$request,TokenInterface$token,$providerKey)
71+
{
72+
// if the user hit a secure page and start() was called, this was
73+
// the URL they were on, and probably where you want to redirect to
74+
$targetPath =$request->getSession()->get('_security.'.$providerKey.'.target_path');
75+
76+
if (!$targetPath) {
77+
$targetPath =$this->getDefaultSuccessRedirectUrl();
78+
}
79+
80+
returnnewRedirectResponse($targetPath);
81+
}
82+
83+
publicfunctionsupportsRememberMe()
84+
{
85+
returntrue;
86+
}
87+
88+
/**
89+
* Override to control what happens when the user hits a secure page
90+
* but isn't logged in yet.
91+
*
92+
* @param Request $request
93+
* @param AuthenticationException|null $authException
94+
*
95+
* @return RedirectResponse
96+
*/
97+
publicfunctionstart(Request$request,AuthenticationException$authException =null)
98+
{
99+
$url =$this->getLoginUrl();
100+
101+
returnnewRedirectResponse($url);
102+
}
103+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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+
namespaceSymfony\Component\Security\Guard\Firewall;
13+
14+
useSymfony\Component\HttpFoundation\Request;
15+
useSymfony\Component\HttpFoundation\Response;
16+
useSymfony\Component\HttpKernel\Event\GetResponseEvent;
17+
useSymfony\Component\Security\Guard\GuardAuthenticatorHandler;
18+
useSymfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
19+
useSymfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
20+
useSymfony\Component\Security\Guard\GuardAuthenticatorInterface;
21+
usePsr\Log\LoggerInterface;
22+
useSymfony\Component\Security\Core\Authentication\Token\TokenInterface;
23+
useSymfony\Component\Security\Core\Exception\AuthenticationException;
24+
useSymfony\Component\Security\Http\Firewall\ListenerInterface;
25+
useSymfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
26+
27+
/**
28+
* Authentication listener for the "guard" system.
29+
*
30+
* @author Ryan Weaver <ryan@knpuniversity.com>
31+
*/
32+
class GuardAuthenticationListenerimplements ListenerInterface
33+
{
34+
private$guardHandler;
35+
private$authenticationManager;
36+
private$providerKey;
37+
private$guardAuthenticators;
38+
private$logger;
39+
private$rememberMeServices;
40+
41+
/**
42+
* @param GuardAuthenticatorHandler $guardHandler The Guard handler
43+
* @param AuthenticationManagerInterface $authenticationManager An AuthenticationManagerInterface instance
44+
* @param string $providerKey The provider (i.e. firewall) key
45+
* @param GuardAuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider
46+
* @param LoggerInterface $logger A LoggerInterface instance
47+
*/
48+
publicfunction__construct(GuardAuthenticatorHandler$guardHandler,AuthenticationManagerInterface$authenticationManager,$providerKey,array$guardAuthenticators,LoggerInterface$logger =null)
49+
{
50+
if (empty($providerKey)) {
51+
thrownew \InvalidArgumentException('$providerKey must not be empty.');
52+
}
53+
54+
$this->guardHandler =$guardHandler;
55+
$this->authenticationManager =$authenticationManager;
56+
$this->providerKey =$providerKey;
57+
$this->guardAuthenticators =$guardAuthenticators;
58+
$this->logger =$logger;
59+
}
60+
61+
/**
62+
* Iterates over each authenticator to see if each wants to authenticate the request.
63+
*
64+
* @param GetResponseEvent $event
65+
*/
66+
publicfunctionhandle(GetResponseEvent$event)
67+
{
68+
if (null !==$this->logger) {
69+
$this->logger->info('Checking for guard authentication credentials.',array('firewall_key' =>$this->providerKey,'authenticators' =>count($this->guardAuthenticators)));
70+
}
71+
72+
foreach ($this->guardAuthenticatorsas$key =>$guardAuthenticator) {
73+
// get a key that's unique to *this* guard authenticator
74+
// this MUST be the same as GuardAuthenticationProvider
75+
$uniqueGuardKey =$this->providerKey.'_'.$key;
76+
77+
$this->executeGuardAuthenticator($uniqueGuardKey,$guardAuthenticator,$event);
78+
}
79+
}
80+
81+
privatefunctionexecuteGuardAuthenticator($uniqueGuardKey,GuardAuthenticatorInterface$guardAuthenticator,GetResponseEvent$event)
82+
{
83+
$request =$event->getRequest();
84+
try {
85+
if (null !==$this->logger) {
86+
$this->logger->info('Calling getCredentials on guard configurator.',array('firewall_key' =>$this->providerKey,'authenticator' =>get_class($guardAuthenticator)));
87+
}
88+
89+
// allow the authenticator to fetch authentication info from the request
90+
$credentials =$guardAuthenticator->getCredentials($request);
91+
92+
// allow null to be returned to skip authentication
93+
if (null ===$credentials) {
94+
return;
95+
}
96+
97+
// create a token with the unique key, so that the provider knows which authenticator to use
98+
$token =newPreAuthenticationGuardToken($credentials,$uniqueGuardKey);
99+
100+
if (null !==$this->logger) {
101+
$this->logger->info('Passing guard token information to the GuardAuthenticationProvider',array('firewall_key' =>$this->providerKey,'authenticator' =>get_class($guardAuthenticator)));
102+
}
103+
// pass the token into the AuthenticationManager system
104+
// this indirectly calls GuardAuthenticationProvider::authenticate()
105+
$token =$this->authenticationManager->authenticate($token);
106+
107+
if (null !==$this->logger) {
108+
$this->logger->info('Guard authentication successful!',array('token' =>$token,'authenticator' =>get_class($guardAuthenticator)));
109+
}
110+
111+
// sets the token on the token storage, etc
112+
$this->guardHandler->authenticateWithToken($token,$request);
113+
}catch (AuthenticationException$e) {
114+
// oh no! Authentication failed!
115+
116+
if (null !==$this->logger) {
117+
$this->logger->info('Guard authentication failed.',array('exception' =>$e,'authenticator' =>get_class($guardAuthenticator)));
118+
}
119+
120+
$response =$this->guardHandler->handleAuthenticationFailure($e,$request,$guardAuthenticator,$this->providerKey);
121+
122+
if ($responseinstanceof Response) {
123+
$event->setResponse($response);
124+
}
125+
126+
return;
127+
}
128+
129+
// success!
130+
$response =$this->guardHandler->handleAuthenticationSuccess($token,$request,$guardAuthenticator,$this->providerKey);
131+
if ($responseinstanceof Response) {
132+
if (null !==$this->logger) {
133+
$this->logger->info('Guard authenticator set success response.',array('response' =>$response,'authenticator' =>get_class($guardAuthenticator)));
134+
}
135+
136+
$event->setResponse($response);
137+
}else {
138+
if (null !==$this->logger) {
139+
$this->logger->info('Guard authenticator set no success response: request continues.',array('authenticator' =>get_class($guardAuthenticator)));
140+
}
141+
}
142+
143+
// attempt to trigger the remember me functionality
144+
$this->triggerRememberMe($guardAuthenticator,$request,$token,$response);
145+
}
146+
147+
/**
148+
* Should be called if this listener will support remember me.
149+
*
150+
* @param RememberMeServicesInterface $rememberMeServices
151+
*/
152+
publicfunctionsetRememberMeServices(RememberMeServicesInterface$rememberMeServices)
153+
{
154+
$this->rememberMeServices =$rememberMeServices;
155+
}
156+
157+
/**
158+
* Checks to see if remember me is supported in the authenticator and
159+
* on the firewall. If it is, the RememberMeServicesInterface is notified.
160+
*
161+
* @param GuardAuthenticatorInterface $guardAuthenticator
162+
* @param Request $request
163+
* @param TokenInterface $token
164+
* @param Response $response
165+
*/
166+
privatefunctiontriggerRememberMe(GuardAuthenticatorInterface$guardAuthenticator,Request$request,TokenInterface$token,Response$response =null)
167+
{
168+
if (null ===$this->rememberMeServices) {
169+
if (null !==$this->logger) {
170+
$this->logger->info('Remember me skipped: it is not configured for the firewall.',array('authenticator' =>get_class($guardAuthenticator)));
171+
}
172+
173+
return;
174+
}
175+
176+
if (!$guardAuthenticator->supportsRememberMe()) {
177+
if (null !==$this->logger) {
178+
$this->logger->info('Remember me skipped: your authenticator does not support it.',array('authenticator' =>get_class($guardAuthenticator)));
179+
}
180+
181+
return;
182+
}
183+
184+
if (!$responseinstanceof Response) {
185+
thrownew \LogicException(sprintf(
186+
'%s::onAuthenticationSuccess *must* return a Response if you want to use the remember me functionality. Return a Response, or set remember_me to false under the guard configuration.',
187+
get_class($guardAuthenticator)
188+
));
189+
}
190+
191+
$this->rememberMeServices->loginSuccess($request,$response,$token);
192+
}
193+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp