Symfony Security Cheat Sheet
如果无法正常显示,请先停止浏览器的去广告插件。
1. Powerful and complete
Security for your apps!
Security
You can use:
- Security Component (Standalone)
- SecurityBundle (integrates Security Component on Symfony)
4.4
By https://andreiabohner.org
Contains sub-components:
Core, Http, Guard, Csrf
Security Component (Standalone)
Install
$ composer require symfony/security-core
Who you are
Authentication
(a token will be
generated to
represent you)
HTTP Security component uses listeners attached to kernel.request to create tokens.
Tokens
Create a token representing the user input
UsernamePasswordToken username and password token
RememberMeToken uses a browser cookie
SwitchUserToken token representing a user who temporarily impersonates another one
AnonymousToken represents an anonymous token
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
$inputToken = new UsernamePasswordToken('john', 'myPassword987', 'default');
often used in
traditional apps
User Providers
ChainUserProvider
input from
the user
Load users from “some resource”
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
calls several providers in a chain until one is
$userProvider = new InMemoryUserProvider([
able to handle the request
InMemoryUserProvider
UsernamePasswordFormAuthenticationListener
creates a UsernamePasswordToken
based on the login form submit
'john' => [
simple non persistent user provider. Useful for testing,
demonstration, prototyping, and for simple needs 'password' => 'myPassword987',
(a backend with a unique admin for instance) 'roles' => ['POST_CREATE']
],
e.g.: fetch users from a PHP array
]);
LdapUserProvider user provider on top of LDAP
MissingUserProvider dummy user provider used to throw proper exception
find the user
$myUser = $userProvider ->loadUserByUsername('john');
when a firewall requires a user provider but none
was defined
You can also create your own custom user provider
Encode a plain text password and
check if the password is valid
Password Encoders
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder;
Argon2iPasswordEncoder
BCryptPasswordEncoder
NativePasswordEncoder
These encoders
do not require a
user-generated salt
use Symfony\Component\Security\Core\User\User;
$encoderFactory = new EncoderFactory([
User::class => new PlaintextPasswordEncoder(),
SodiumPasswordEncoder
MigratingPasswordEncoder
MessageDigestPasswordEncoder
Pbkdf2PasswordEncoder
PlaintextPasswordEncoder
UserPasswordEncoder
Hashes passwords
using the best
available encoder
]);
get the encoder associated with this user
(other users can use other encoders)
$encoderFactory ->getEncoder(User::class)
->isPasswordValid($myUser->getPassword(), 'myPassword987', '');
check if matches the
user's password
2. By https://andreiabohner.org
Security
AuthenticationManagerInterface
is responsible for this
Authenticate the Token: AuthenticationManager
4.4
AuthenticationProviderManager - authentication manager based on authentication providers:
Transform an unauthenticated token ( user input )
into an authenticated token (security identity)
Authentication Providers
AnonymousAuthenticationProvider validates AnonymousToken instances. Always returns a token representing an anonymous user
DaoAuthenticationProvider uses a user provider (UserProviderInterface) to retrieve a user matching the input and then matches
most used
the password (using a password encoder UsernamePasswordToken).
LdapBindAuthenticationProvider authenticates a user against an LDAP server
RememberMeAuthenticationProvider authenticates a remember-me cookie
SimpleAuthenticationProvider deprecated since Symfony 4.2, use Guard instead
Instantiate the Authentication Manager
After:
- create a token representing the user input
- load the user from some User Provider
- encode & check the password
we can create the AuthenticationProviderManager
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider;
use Symfony\Component\Security\Core\User\UserChecker;
$authenticationManager = new AuthenticationProviderManager([
new DaoAuthenticationProvider(
$userProvider ,
Check some “user flags” after the user is fetched from
user provider (e.g. if the user is activated, ...)
new UserChecker(),
'default',
$encoderFactory
),
]);
The provider key (same provided when create the token).
Used to make sure the token is from our app and
to know which provider should handle the token
Authenticate the Token
Create an authenticated token
$authenticatedToken = $authenticationManager-> authenticate ( $inputToken );
echo 'Hi ' .$authenticatedToken ->getUsername();
what you are allowed to do
Authorization
(determine whether or not you
have access to something)
Authorize Actions: AccessDecisionManager
The default implementation uses “Security voters” to decide whether the user is allowed to execute an action.
These voters are provided with an attribute (representing the action) and optionally some context (the subject of the action).
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter;
$accessDecisionManager = new AccessDecisionManager([
this voter checks if
new RoleVoter('POST_'),
the User’s getRoles()
POST_ is the prefix an
contains the provided ]);
attribute must have in order
attribute
to be managed by this voter
$isSupervisor = $accessDecisionManager->decide(
$authenticatedToken ,
['POST_CREATE']
);
uses the access decision manager
to see if the authenticated token
has the “POST_CREATE” role
Voters
The default Symfony voters don’t
validate an action, but validate
the user’s identity.
AuthenticatedVoter
ExpressionVoter
RoleVoter
RoleHierarchyVoter
3. By https://andreiabohner.org
Security
Install
$ composer require symfony/security-bundle
Symfony (SecurityBundle )
4.4
Integrates the Security
Component on Symfony apps
Authentication
The User Class
namespace Symfony\Component\Security\Core\User;
use Symfony\Component\Security\Core\Role\Role;
The easiest way to create the User
class is to use the MakerBundle
interface UserInterface
{
$ php bin/console make:user
/**
The name of the security user class (e.g. User) [User]:
* Returns the roles granted to the user.
Call the class: User
> User
*
Do you want to store user data in the database (via Doctrine)? (yes/no)[yes]: * public function getRoles()
> yes * {
e.g. config to store user info in the database
*
return ['ROLE_USER'];
Enter a property name that will be the unique "display" name for the user *
(e.g. email, username, uuid [email] *
> email * Alternatively, the roles might be stored on a ``roles`` property,
}
* and populated in any number of different ways when the user object
Does this app need to hash/check user passwords? (yes/no) [yes]: * is created.
> yes *
* @return (Role|string)[] The user roles
created: src/Entity/User.php
*/
created: src/Repository/UserRepository.php
public function getRoles();
updated: src/Entity/User.php
/**
updated: config/packages/security.yaml
* Returns the password used to authenticate the user.
configured one User Provider
in your security.yaml file
under the providers key
*
* This should be the encoded password. On authentication, a plain-text
* password will be salted, encoded, and then compared to this value.
*
* @return string The password
use Symfony\Component\Security\Core\User\UserInterface;
*/
public function getPassword();
/**
/**
* @ORM\Entity(repositoryClass="App\Repository\UserRepository") * Returns the salt that was originally used to encode the password.
*/ *
class User implements UserInterface
{
The User class must
implement UserInterface
* This can return null if the password was not encoded using a salt.
*
// ...
* @return string|null The salt
}
*/
Visual identifier that represents the user (isn’t
the username), could be an email for e.g.
Only used to display who is currently logged in
on the web debug toolbar. public function getSalt();
public function getUsername(): string
{
return (string) $this->email;
} *
/**
* Returns the username used to authenticate the user.
* @return string The username
*/
public function getUsername() ;
Enable the User Class as a User Provider
/**
# config/packages/security.yaml
security:
* Removes sensitive data from the user.
*
providers:
* This is important if, at any given point, sensitive information like
app_user_provider:
* the plain-text password is stored on this object.
entity:
class: App\Entity\User */
property: email public function eraseCredentials();
}
4. By https://andreiabohner.org
Security
4.4
Load users from some resource, reload User data from the session, and some
other optional features, like remember me, and impersonation (switch_user).
Configured under “providers” key in security.yml
User Providers
Using a Custom Query to Load the User
Entity User Provider loads users from database
LDAP User Provider loads users from LDAP server
Memory User Provider loads users from configuration file
Chain User Provider merges two or more user providers into a new user provider
Common for traditional web apps.
Users are stored in a database
and the user provider uses
Doctrine to retrieve them
Entity User Provider
e.g. you want to find a user by email or username
Memory User Provider
Useful in app prototypes.
Stores all user information
in a configuration file,
including their passwords
implement
this interface
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
providers:
users:
the class of the entity
entity:
that represents users
class: 'App\Entity\User'
property: 'username'
the property used to query by.
# manager_name: 'customer' (can only query from one field)
optional: if you're using multiple Doctrine entity
managers, this option defines which one to use
// src/Repository/UserRepository.php
namespace App\Repository;
class UserRepository extends EntityRepository implements UserLoaderInterface
{
// ...
define the logic in this method
public function loadUserByUsername($usernameOrEmail)
{
return $this->createQueryBuilder('u')
->where('u.username = :query OR u.email = :query')
->setParameter('query', $usernameOrEmail)
->getQuery()
HEADS UP! remove the
->getOneOrNullResult();
property key from the entity
}
providers:
}
backend_users:
memory:
users:
john_admin: { password: '$2y$13$a...', roles: ['ROLE_ADMIN'] }
jane_admin: { password: '$2y$13$C...', roles: ['ROLE_ADMIN', 'ROLE_SUPER_ADMIN'] }
provider in security.yaml
LDAP User Provider
configure the LDAP client
providers:
# config/services.yaml
in services.yaml
my_ldap:
services:
ldap:
Symfony\Component\Ldap\Ldap:
service: Symfony\Component\Ldap\Ldap
arguments: ['@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
base_dn: dc=example,dc=com
Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
search_dn: "cn=read-only-admin,dc=example,dc=com"
arguments:
search_password: password
-
host: my-server
default_roles: ROLE_USER
port: 389
uid_key: uid
encryption: tls
$ composer require symfony/ldap
Chain User Provider
providers:
backend_users:
memory:
# ...
install
Combines two or more
user providers (entity,
memory, and LDAP) to
create a new user provider
legacy_users:
entity:
# ...
users:
entity:
# ...
all_users:
chain:
providers: ['legacy_users', 'users', 'backend']
options:
protocol_version: 3
referrals: false
How Users are Refreshed from Session
End of every request User object is serialized to the session
Beginning of the next request User object it's deserialized & passed
to the user provider to “refresh" it
(e.g. Doctrine queries the DB for
By default, the core
AbstractToken class
compares the return
values of the:
getPassword()
getSalt()
getUsername()
a fresh user).
Then, the original User object from the
session and the refreshed User object
are "compared" to see if they are "equal".
If any of these are different, your user
will be logged out.
5. By https://andreiabohner.org
Security
4.4
Custom User Provider
if you're loading users from a custom location
(e.g. legacy database connection),
you'll need to create a custom user provider
// src/Security/UserProvider.php
namespace App\Security;
use
use
use
use
Symfony\Component\Security\Core\Exception\UnsupportedUserException;
Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
Symfony\Component\Security\Core\User\UserInterface;
Symfony\Component\Security\Core\User\UserProviderInterface;
Enable the Custom User Provider
# config/packages/security.yaml
class UserProvider implements UserProviderInterface
security:
the name of your
{
user provider can
providers:
/**
be anything
* Symfony calls this method if you use features like switch_user
your_custom_user_provider:
* or remember_me.
id: App\Security\UserProvider
*
* If you're not using these features, you don’t need to implement
* this method.
*
* @return UserInterface
*
* @throws UsernameNotFoundException if the user is not found
*/
public function loadUserByUsername($username)
{
// Load a User object from your data source or throw UsernameNotFoundException.
// The $username argument may not actually be a username:
// it is whatever value is being returned by the getUsername() method in your User class.
throw new \Exception('TODO: fill in loadUserByUsername() inside '.__FILE__);
}
/**
* Refreshes the user after being reloaded from the session.
*
* When a user is logged in, at the beginning of each request, the
* User object is loaded from the session and then this method is
* called. Your job is to make sure the user's data is still fresh by,
* for example, re-querying for fresh User data.
*
* If your firewall is "stateless: true" (for a pure API), this method is not called.
*
* @return UserInterface
*/
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
}
// Return a User object after sure its data is "fresh" or throw a UsernameNotFoundException if user no longer exists
throw new \Exception('TODO: fill in refreshUser() inside '.__FILE__);
}
public function supportsClass($class)
{
return User::class === $class;
}
}
tells Symfony to use this
provider for this User class
6. By https://andreiabohner.org
Security
Use the UserPasswordEncoderInterface service to
encode and check passwords. E.g. using fixtures:
4.4
Encoding Passwords
you can control how
passwords are encoded
in security.yaml
Defines the algorithm used to encode passwords.
// src/DataFixtures/UserFixtures.php
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class UserFixtures extends Fixture
{
private $passwordEncoder;
If your app defines more than one user class,
each of them can define its own encoding algorithm.
public function __construct(UserPasswordEncoderInterface $passwordEncoder)
{
security:
$this->passwordEncoder = $passwordEncoder;
your user class name
# ...
}
encoders:
bcrypt or sodium are
recommended. sodium
algorithm: bcrypt is more secure, but
requires PHP 7.2 or
cost: 12
the Sodium extension
App\Entity\User:
public function load(ObjectManager $manager)
{
$user = new User();
$user->setPassword($this->passwordEncoder->encodePassword(
$user,
security:
'the_new_password'
# ...
));
recommended:
encoders:
App\Entity\User:
algorithm: auto
will use the best
algorithm available
on your system
}
}
Manually Encode a Password
$ php bin/console security:encode-password
Authenticating Users
Instead of building a route & controller to handle login, you'll activate an authentication provider:
some code that runs automatically before your controller is called.
Authentication Providers
form_login
At the beginning of every request,
Symfony calls a set of "authentication listeners",
or "authenticators"
http_basic
LDAP via HTTP Basic or Form Login
json_login
X.509 Client Certificate Authentication (x509)
REMOTE_USER Based Authentication (remote_user)
simple_form
simple_pre_auth
Guard Authenticator
recommended:
allows you to control every part
of the authentication process
If your application logs users in via a third-party service such as Google, Facebook or Twitter (social login),
check out the HWIOAuthBundle community bundle.
Comparing Users Manually with EquatableInterface
If you need more control over the "compare users" process, make your User class implement EquatableInterface.
Then, your isEqualTo() method will be called when comparing users.
7. By https://andreiabohner.org
Security
Guard Authentication Provider
4.4
$ php bin/console make:auth
Create a Login Form Authenticator
create an
authenticator
namespace App\Security;
...
Enable the Authenticator
class LoginFormAuthenticator extends AbtractFormLoginAuthenticator
{
You can use AbstractGuardAuthenticator
instead to create an API authenticator
class used when you choose
“Login form authenticator”
on make:auth command
# config/packages/security.yaml
When activated, at the beginning
firewalls:
of every request, the supports()
main:
RouterInterface $router, CsrfTokenManagerInterface $csrfTokenManager,
method of the authenticator will
be called
guard:
UserPasswordEncoderInterface $passwordEncoder)
authenticators:
public function __construct(UserRepository $userRepository,
{
- App\Security\LoginFormAuthenticator
...
$this->csrfTokenManager = $csrfTokenManager;
}
if return:
false - nothing else happens. It doesn't call any
other methods on the authenticator
true - call getCredentials()
public function supports(Request $request)
{
// do your work when we're POSTing to the login page
return $request->attributes->get('_route') === 'login'
&& $request->isMethod('POST');
}
read the authentication credentials of
the request and return them.
Call getUser() and pass this array back
to us as the first $credentials argument:
public function getCredentials(Request $request)
{
CSRF Protection
return [
'email' => $request->request->get('email'),
templates/security/login.html.twig
'password' => $request->request->get('password'),
...
<input type="hidden" name="_csrf_token"
'csrf_token' => $request->request->get('_csrf_token'),
];
value="{{ csrf_token('authenticate') }}">
...
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
Use the $credentials to return a User object,
or null if the user isn't found.
if return:
null - the authentication process stop,
and the user will see an error.
User object - calls checkCredentials(), and passes to
it the same $credentials and User object
return $this->userRepository->findOneBy(['email' => $credentials['email']]);
}
check to see if the user's password is
correct, or any other security checks.
public function checkCredentials($credentials, UserInterface $user)
{
}
if return:
false - authentication would fail and the user
see an "Invalid Credentials" message.
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
true - authentication is successful,
calls onAuthenticationSuccess()
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
where to redirect after
a successful login
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
if there is a referer, redirect
to it, if not, to homepage
return new RedirectResponse($this->router->generate('homepage'));
}
protected function getLoginUrl()
{
return $this->router->generate('login');
}
if return:
- Response object: will be immediately sent back to the user
- nothing: the request would continue to the controller
on failure, the authenticator class calls
getLoginUrl() and try to redirect here
8. By https://andreiabohner.org
Security
Guard Authentication Provider
4.4
Guard Authenticator Methods
supports(Request $request)
getCredentials(Request $request)
getUser($credentials, UserProviderInterface $userProvider)
checkCredentials($credentials, UserInterface $user)
onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
onAuthenticationFailure(Request $request, AuthenticationException $exception)
start(Request $request, AuthenticationException $authException = null)
supportsRememberMe()
you don’t need to handle
these 3 methods when using
AbtractFormLoginAuthenticator.
They are handled automatically
Login and Logout Methods
// src/Controller/SecurityController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
/**
* @Route("/login", name="login")
the route name compared in
supports method of
LoginFormAuthenticator class
*/
public function login(AuthenticationUtils $authenticationUtils)
{
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', [
get the login error
if there is one
last username entered
by the user
'last_username' => $lastUsername,
'error'
=> $error,
]);
}
/**
* @Route("/logout", name="logout")
*/
just write the method and add the
path defined in security.yaml
Symfony will automatically log the
user out and then redirect them
public function logout()
{
}
}
Control What Happens After Logout Display Login Error Messages in Templates
# config/packages/security.yaml
security: templates/security/login.html.twig
firewalls:
main:
logout:
{% if error %}
point it to a service id of
a class that implements
LogoutSuccessHandlerInterface
path: logout
success_handler: logout_success_handler
{{ error.messageKey|trans(error.messageData, 'security') }}
{% endif %}
9. By https://andreiabohner.org
Security
Enable it
4.4
# config/packages/security.yaml
security:
firewalls:
Remember Me
This is the special name
that Symfony uses
main:
remember_me:
secret:
<input type="checkbox" name="_remember_me"> Remember me
'%kernel.secret%'
lifetime: 2592000 # 30 days in seconds
Impersonating a User
You can go to any URL and add ?_switch_user= and the user identifier (e.g. email) of an user that you want to impersonate.
http://example.com/somewhere?_switch_user=john@example.com
Enable it Find the Original User
security:
firewalls:
main:
switch_user: true $token = $this->security->getToken();
Requires you to have the ROLE_ALLOWED_TO_SWITCH }
security:
role_hierarchy:
ROLE_ADMIN: [ROLE_ALLOWED_TO_SWITCH] Knowing when Impersonation is Active
if ($token instanceof SwitchUserToken) {
$impersonatorUser = $token->getOriginalToken()->getUser();
Switch Back to the Original User
?_switch_user=_exit
http://example.com/somewhere?_switch_user=_exit
Custom User Checker
When we are switched to another user, Symfony gives us a special role called
ROLE_PREVIOUS_ADMIN
{% if is_granted('ROLE_PREVIOUS_ADMIN') %}
<a href="{{ path('homepage', {'_switch_user': '_exit'}) }}">Exit</a>
{% endif %}
if you need additional checks
before and after user authentication
namespace App\Security;
use
use
use
use
use
App\Security\User as AppUser;
Symfony\Component\Security\Core\Exception\AccountExpiredException;
App\Exception\AccountDeletedException;
Symfony\Component\Security\Core\User\UserCheckerInterface;
Symfony\Component\Security\Core\User\UserInterface;
must implement
class UserChecker implements UserCheckerInterface
UserCheckerInterface
{
public function checkPreAuth(UserInterface $user)
{
if (!$user instanceof AppUser) {
return;
}
user is deleted, show a generic
Account Not Found message
if ($user->isDeleted()) {
# config/packages/security.yaml
throw new AccountDeletedException();
security:
}
defined per firewall
firewalls:
}
main:
pattern: ^/
public function checkPostAuth(UserInterface $user)
user_checker: App\Security\UserChecker
{
if (!$user instanceof AppUser) {
return;
}
user account is expired,
the user may be notified
if ($user->isExpired()) {
Enable it
throw new AccountExpiredException('...');
}
}
}
10. By https://andreiabohner.org
Security
Authorization
4.4
Decide if a user can access some resource
This decision will be made by an instance
of AccessDecisionManagerInterface
The Authorization Process Consists of:
1. Add roles: user receives a specific set of roles when logging in (e.g. ROLE_ADMIN)
2. Check permissions: a resource (e.g. URL, controller) requires a specific role (like ROLE_ADMIN) to be accessed
1. ROLES (define what the user can access)
When a user logs in, the getRoles() method on your User
object is called to determine which roles the user has
Are strings used to grant access to users (e.g. "edit a blog post", "create an invoice"). You can freely choose those strings.
The only requirement is that they must start with ROLE_ (e.g. ROLE_POST_EDIT, ROLE_INVOICE_CREATE).
you have to return at least one role
(e.g. ROLE_USER) for the user
Standard ROLES
ROLE_USER
ROLE_PREVIOUS_ADMIN Added when we are switched to another user
ROLE_ADMIN ROLE_ALLOWED_TO_SWITCH Allow switch to another user
ROLE_SUPER_ADMIN ROLE_YOUR_DEFINED_NAME
Add to all logged users
Special “ROLES”
you can use these anywhere roles are used: like access_control, controller or in Twig.
you can use these roles to check if a user is logged in
IS_AUTHENTICATED_REMEMBERED All logged in users have this. Even if you don't use the remember me functionality, you can use this to check if the user is logged in
IS_AUTHENTICATED_FULLY Users who are logged in only because of a "remember me" have IS_AUTHENTICATED_REMEMBERED but not have IS_AUTHENTICATED_FULLY
IS_AUTHENTICATED_ANONYMOUSLY All users (even anonymous ones) have this
Calls the "voter" system
Symfony takes the responses from all voters and makes
the final decision (allow or deny access to the resource)
according to the strategy defined (affirmative, consensus
or unanimous
2. Checking Permissions (handle authorization)
- for protecting broad URL patterns, use access_control in security.yaml
- whenever possible, use the @Security annotation in your controller
- check security directly on the security.authorization_checker service (isGranted) for complex situations
- define a custom security voter to implement fine-grained restrictions
Checking Permissions in the Controller
You can use:
- annotations (@Security or @IsGranted)
- methods (denyAccessUnlessGranted() or isGranted())
Using @Security Annotation
use App\Entity\Post;
Using @IsGranted Annotation
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
/**
* @IsGranted("ROLE_ADMIN")
* @Security("is_granted('ROLE_ADMIN')")
*/
public function new()
require ROLE_ADMIN for
*every* method in this class
*/
class AdminController extends AbstractController
{
{
/**
}
* @IsGranted("ROLE_ADMIN")
*/
/**
public function adminDashboard()
* @Security("user.getEmail() == post.getAuthorEmail()")
{
*/
public function edit(Post $post)
{
}
}
}
require ROLE_ADMIN
for only this method
11. By https://andreiabohner.org
Security
4.4
Using isGranted() and denyAccessUnlesssGranted() Methods
Equivalent code without using the "denyAccessUnlessGranted()" shortcut:
if (!$post->isAuthor($this->getUser())) {
$this->denyAccessUnlessGranted('edit', $post);
}
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface
...
If access is not granted, a
AccessDeniedException is thrown:
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
- not logged: redirect to the login page
- logged in: show the 403 access denied page
}
...
$this->denyAccessUnlessGranted('ROLE_ADMIN');
if (!$this->authorizationChecker->isGranted('edit', $post)) {
$hasAccess = $this->isGranted('ROLE_ADMIN');
throw $this->createAccessDeniedException();
}
Checking Permissions in Templates (Twig)
{% if is_granted('ROLE_USER') %}
....
{% else %}
Get the User Who is Logged In
Controller Template (Twig)
$user = $this->getUser(); {{ app.user.firstName }}
<a href="{{ path('app_login') }}">Login</a>
Service
{% endif %}
use Symfony\Component\Security\Core\Security;
Access Decision Manager
deciding whether or not
a user is authorized to
perform a certain action
class SomeService
{
private $security;
public function __construct(Security $security)
Depends on multiple voters, and makes a final verdict based on
all the votes (either positive, negative or neutral) it has received.
It recognizes several strategies:
{
$this->security = $security;
}
affirmative (default) grant access as soon as there is one voter
public function someMethod(): string
granting access
consensus
{
grant access if there are more voters granting
$user = $this->security->getUser();
access than there are denying
unanimous
only grant access if none of the voters has
denied access
}
}
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
$voters = [...]; instances of
Symfony\Component\Security\Core\Authorization\Voter\VoterInterface
$strategy = ...; one of "affirmative", "consensus", "unanimous”
$allowIfAllAbstainDecisions = ...;
whether or not to grant access when all voters abstain
$allowIfEqualGrantedDeniedDecisions = ...;
whether or not to grant access when there is no
majority (only to the "consensus" strategy)
$accessDecisionManager = new AccessDecisionManager(
$voters,
$strategy,
$allowIfAllAbstainDecisions,
$allowIfEqualGrantedDeniedDecisions
);
Change the Default Strategy
# config/packages/security.yaml
security:
access_decision_manager:
strategy: unanimous
allow_if_all_abstain: false
12. By https://andreiabohner.org
Security
Are the most granular
way of checking permissions
4.4
Voters
Creating a Custom Voter
When your security
logic is complex use
custom voters
namespace App\Security;
use
use
use
use
use
App\Entity\Post;
Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
Symfony\Component\Security\Core\Authorization\Voter\Voter;
Symfony\Component\Security\Core\User\UserInterface;
class PostVoter extends Voter
{
const CREATE = 'create';
const EDIT
= 'edit';
private $decisionManager;
private $security;
or implement VoterInterface
In the Http component, an AccessListener
checks access using this manager based
on the configured access_control rules
public function __construct(AccessDecisionManagerInterface $decisionManager,
Security $security)
{
$this->decisionManager = $decisionManager;
$this->security = $security;
When isGranted() or denyAccessUnlessGranted() is called,
}
the first argument is passed here as $attribute
(e.g. ROLE_USER, edit) and the second argument (if any)
is passed as $subject (e.g. null, a Post object)
protected function supports($attribute, $subject)
{
if (!in_array($attribute, [self::CREATE, self::EDIT])) {
return false;
}
if (!$subject instanceof Post) {
return false;
}
return true;
If return false this voter is done:
some other voter should process this
Using the Custom Voter
you can use the voter with
the @Security annotation:
If return true, voteOnAttribute() will be called
}
/**
* @Security("is_granted('edit', post)")
*/
public function edit(Post $post)
{
// ...
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
The $token can be used to find
$user = $token->getUser();
the current user object (if any)
if (!$user instanceof UserInterface) {
return false;
the user must be logged in; if not, deny access
}
You can also use this directly with the
security.authorization_checker service or via the
even easier shortcut in a controller:
if ($this->security->isGranted('ROLE_SUPER_ADMIN')) {
return true;
}
Checking for Roles inside a Voter
/** @var Post $post */
$post = $subject;
you know $subject is a Post object,
thanks to supports method
switch ($attribute) {
case self::CREATE:
if ($this->decisionManager->decide($token, ['ROLE_ADMIN'])) {
return true;
if the user is an admin,
}
allow them to create new posts
break;
case self::EDIT:
if ($user->getEmail() === $post->getAuthorEmail()) {
return true;
if the user is the author of
}
the post, allow them to edit the posts
break;
}
return false;
}
}
return:
true - to allow access
false - to deny access
/**
* @Route("/{id}/edit", name="admin_post_edit")
*/
public function edit($id)
{
$post = ...; // query for the post
$this->denyAccessUnlessGranted('edit', $post);
}
13. By https://andreiabohner.org
Security
4.4
Where the security
system is configured
SecurityBundle Configuration
(security.yaml)
# config/packages/security.yaml
security:
where user is redirected after a 403 HTTP error
(unless there is a custom access deny handler)
access_denied_url: null
always_authenticate_before_granting: false
if true: eraseCredentials() method of the user object is called after authentication
hide_user_not_found: true
if true: when a user isn’t found a generic BadCredentialsException exception is thrown w/ msg "Bad credentials"
if false: UsernameNotFoundException exception is thrown and includes the given not found username
session_fixation_strategy: migrate
user providers
app_user_provider:
entity:
class: App\Entity\User
property: email
encoders:
password encoders
App\Entity\User:
algorithm: auto
App\Entity\User: 'bcrypt'
use the best possible algorithm
available on your system
bcrypt encoder with
default options
App\Entity\User:
algorithm: 'bcrypt'
cost:
App\Entity\User:
Password Encoders
bcrypt encoder with
custom options
15
App\Entity\User: 'sodium'
sodium encoder with
default options
'sodium' sodium encoder with
custom options
memory_cost: 16384 Amount in KiB (16384=16 MiB)
time_cost: 2 Number of iterations
threads: 4 Number of parallel threads
algorithm:
NONE: session isn’t changed
MIGRATE: session id is updated, attributes are kept
INVALIDATE: session id is updated, attributes are lost
protection against session fixation.
Possible values:
providers:
users:
entity:
class: 'App\Entity\User'
property: 'username'
# manager_name: 'customer'
my_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: dc=example,dc=com
search_dn: "cn=read-only-admin,dc=example,dc=com"
...
backend_users:
memory:
users:
user: {password: userpass, roles: ['ROLE_USER']}
admin: {password: adminpass, roles: ['ROLE_ADMIN']}
custom_user_provider:
id: App\Security\UserProvider
all_users:
chain:
providers: ['my_ldap', 'users', 'backend']
User Providers
erase_credentials: true
providers:
if true: user is asked to authenticate before each call
to the isGranted() in services, controllers, or templates
Different Password Encoder for Each User
// src/Acme/UserBundle/Entity/User.php
App\Entity\User:
algorithm: argon2i
argon2i encoder with
custom options
namespace Acme\UserBundle\Entity;
memory_cost: 256 use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface;
time_cost: 1 use Symfony\Component\Security\Core\User\UserInterface;
PBKDF2 encoder using SHA512
hashing with default options
threads: 2
App\Entity\User: 'sha512'
app_encoder:
custom named encoder:
create your own password
encoders as services
class User implements UserInterface, EncoderAwareInterface
{
public function getEncoderName()
{
if ($this->isAdmin()) {
id: 'App\Security\Encoder\MyCustomPasswordEncoder’
return 'extra_secure';
use the ‘extra_secure’
encoder only for
admin users
}
extra_secure:
algorithm: sodium
memory_cost: 16384
time_cost: 2
threads: 4
return null;
}
}
use the default encoder
14. By https://andreiabohner.org
Security
4.4
Firewalls are listeners of the HTTP component
that defines the authentication mechanism used
for each URL (or URL pattern) of your app
all firewalls are one AuthenticationProviderManager
(and thus, one security system)
firewalls:
main:
name of
the firewall
name of the firewall (can be chosen freely)
# ...
impersonating users can be done by
activating the switch_user firewall listener
switch_user: true
# switch_user:
Authentication
main:
# role: ROLE_ADMIN
# parameter: _change_user
your web server is doing all the
authentication process itself
x509:
provider: your_user_provider
remote_user:
allow change the ROLE
and query string used
provider: your_user_provider
simple_preauth:
# ...
# AnonymousAuthenticationProvider
allow anonymous requests so
anonymous: true
users can access public pages
multiple guard authenticators
using shared (one) entry point
guard:
authenticators:
- App\Security\LoginFormAuthenticator
# Use UsernamePasswordToken & DaoAuthenticationProvider
- App\Security\FacebookConnectAuthenticator
form_login: true
entry_point: App\Security\LoginFormAuthenticator
logout:
form_login:
path:
app_logout
where to redirect
after logout
target: app_any_route
login_path: /login
handles a login form POST
automatically
check_path: /login_check
success_handler: logout_success_handler
csrf_token_generator: security.csrf.token_manager
csrf_parameter: _csrf_token
csrf_token_id: a_private_string
default: one year
secret: '%kernel.secret%'
lifetime: 604800 # 1 week in seconds
path:
/
domain: null
secure: false
if set to ‘strict’, the cookie will not
be sent with cross-site requests
httponly: true
samesite: null
remember_me_parameter: _remember_me
catch_exceptions: false
Authentication Providers
remember_me:
default_target_path: /after_login_route_name
always_use_default_target_path: false
use_referer: false
failure_path: login_failure_route_name
target_path_parameter: _target_path
failure_path_parameter: back_to
username_parameter: _username
password_parameter: _password
post_only: true
name of the
password field
if true: user will be forwarded
use_forward: false to the login form instead of
form_login_ldap:
redirected
token_provider: token_provider_id
#always_remember_me: true
name of the
username field
always enable
remember me
service: Symfony\Component\Ldap\Ldap
dn_string: 'uid={username},dc=example,dc=com'
stateless: false
json_login:
enable custom user checker
check_path:
login
username_path: security.credentials.login
user_checker: App\Security\UserChecker
password_path: security.credentials.password
simple_form:
# ...
Restrict Firewalls to a Request
http_basic:
asks credentials (username & password)
using a dialog in the browser.
You cannot use logout with http_basic
realm: Secured Area
security:
firewalls:
http_basic_ldap:
name of the firewall
service: Symfony\Component\Ldap\Ldap
secured_area:
pattern: ^/admin
dn_string: 'uid={username},dc=example,dc=com'
by path
host: ^admin\.example\.com$
methods: [GET, POST]
http_digest:
by host
# ...
by HTTP methods
request_matcher: app.firewall.secured_area.request_matcher
'pattern' is a regexp matched
against the request URL.
If there's a match,
authentication is triggered
by service
name of the firewall
api:
pattern: ^/api/
guard:
multiple guard authenticators
using separate entry points (firewall)
authenticators:
- App\Security\ApiTokenAuthenticator
15. By https://andreiabohner.org
Security
4.4
Only one path will be matched per request:
Symfony starts at the top of the list and as soon as it
finds one access control that matches the URL,
it uses that and stops.
The order of paths
is important!
Each access_control can also match on IP address, hostname
and HTTP methods. It can also be used to redirect a user
to the https version of a URL pattern
access_control:
Matching Options can be:
- path
- ip or ips (netmasks are supported)
- port
- host
- methods
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips : [127.0.0.1, ::1, 192.168.0.1/24] }
Authorization
- { path: ^/internal, roles: ROLE_NO_ACCESS }
‘ips' option supports IP addresses and subnet masks
-
path: ^/_internal/secure
allow_if: "'127.0.0.1' == request.getClientIp() or is_granted('ROLE_ADMIN')”
using an expression
# matches /admin/users/*
- { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
# matches /admin/* except for anything matching the above rule
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/profile, roles: ROLE_USER }
- { path: ^/admin, roles: ROLE_USER_IP, ip : 127.0.0.1 }
- { path: ^/admin, roles: ROLE_USER_PORT, ip : 127.0.0.1, port : 8080 }
- { path: ^/admin, roles: ROLE_USER_HOST, host : symfony\.com$ }
- { path: ^/admin, roles: ROLE_USER_METHOD, methods : [POST, PUT] }
- { path: ^/admin, roles: ROLE_USER }
- { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
Instead of giving many roles to each user, you can define
role inheritance rules by creating a role hierarchy
role_hierarchy:
ROLE_ADMIN:
force redirect to HTTPs
ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
access_decision_manager:
strategy: unanimous
change the default access decision strategy
(decide whether or not a user is authorized
to perform a certain action using voters)
allow_if_all_abstain: false
# displays the default config values defined by Symfony
$ php bin/console config:dump-reference security
Console
# displays the actual config values used by your app
$ php bin/console debug:config security
Standard Voters
AuthenticatedVoter
checks if the token is fully authenticated, anonymous, ...
votes if IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED, or IS_AUTHENTICATED_ANONYMOUSLY is present.
ExpressionVoter votes based on the evaluation of an expression created with the ExpressionLanguage component
RoleVoter votes if any attribute starts with a given prefix. (supports attributes starting with ROLE_ and grants access to the user when
the required ROLE_* attributes can all be found in the array of roles returned by the token's getRoleNames() method)
RoleHierarchyVoter
understands hierarchies in roles (e.g. “admin is a user”). Extends RoleVoter and uses a RoleHierarchy to determine the roles granted to the
user before voting