Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Sécurité avec Symfony

Sécurité avec Symfony

Présentation faite au SfPot Paris, juillet 2015.

Sarah KHALIL

July 23, 2015
Tweet

More Decks by Sarah KHALIL

Other Decks in Technology

Transcript

  1. WHAT DO WE HAVE TO WRITE? security: encoders: Symfony\Component\Security\Core\User\User: bcrypt

    providers: in_memory: memory: users: sarah: password: $2a$12$LCY0M… roles: 'ROLE_USER' firewalls: admin: provider: in_memory pattern: /^admin form_login: login_path: /login check_path: /login_check
  2. WHAT DO WE HAVE TO WRITE? security: encoders: Symfony\Component\Security\Core\User\User: bcrypt

    providers: in_memory: memory: users: sarah: password: $2a$12$LCY0M… roles: 'ROLE_USER' firewalls: admin: provider: in_memory pattern: /^admin form_login: login_path: /login check_path: /login_check
  3. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); Encoder Factory $factory-­‐>getEncoder($user); $encoder $encoder-­‐>isPasswordValid('mypwd'); Encoder true authenticated false | 401: Unauthorized /admin/myprofile or
  4. Your application all routes begin with /admin all routes beginning

    with /shop all routes beginning with /blog
  5. Your application all routes begin with /admin all routes beginning

    with /shop all routes beginning with /blog all other routes…
  6. Your application all routes begin with /admin all routes beginning

    with /shop all routes beginning with /blog all other routes… The user needs to be authenticated to access this part of the app
  7. WHAT DO WE HAVE TO WRITE? security: firewalls: admin: provider:

    in_memory pattern: /^admin form_login: login_path: /login check_path: /login_check
  8. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider authenticated 401: Unauthorized /admin/myprofile or
  9. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); authenticated 401: Unauthorized /admin/myprofile or
  10. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); authenticated 401: Unauthorized /admin/myprofile or
  11. interface UserProviderInterface { /** * Loads the user for the

    given username. * * This method must throw UsernameNotFoundException if the user is not * found. */ public function loadUserByUsername($username); /** * Refreshes the user for the account interface. * * It is up to the implementation to decide if the user data should be * totally reloaded (e.g. from the database), or if the UserInterface * object can just be merged into some internal array of users / identity * map. */ public function refreshUser(UserInterface $user); /** * Whether this provider supports the given user class. */ public function supportsClass($class); }
  12. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    authenticated Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); /admin/myprofile
  13. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory authenticated Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); /admin/myprofile
  14. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory $factory-­‐>getEncoder($user); authenticated Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); /admin/myprofile
  15. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory $factory-­‐>getEncoder($user); $encoder authenticated Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); /admin/myprofile
  16. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory $factory-­‐>getEncoder($user); $encoder Encoder authenticated Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); /admin/myprofile
  17. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory $factory-­‐>getEncoder($user); $encoder Encoder $encoder-­‐>isPasswordValid('mypwd'); authenticated Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); /admin/myprofile
  18. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory $factory-­‐>getEncoder($user); $encoder Encoder $encoder-­‐>isPasswordValid('mypwd'); authenticated true Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); /admin/myprofile
  19. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory $factory-­‐>getEncoder($user); $encoder Encoder $encoder-­‐>isPasswordValid('mypwd'); authenticated true Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); /admin/myprofile false | 401: Unauthorized or
  20. interface PasswordEncoderInterface { /** * Encodes the raw password. */

    public function encodePassword($raw, $salt); /** * Checks a raw password against an encoded password. */ public function isPasswordValid($encoded, $raw, $salt); }
  21. security: encoders: Symfony\Component\Security\Core\User\User: bcrypt providers: in_memory: memory: users: sarah: password:

    $2a$12$LCY0M… roles: 'ROLE_USER' firewalls: admin: provider: in_memory pattern: /^admin form_login: login_path: /login check_path: /login_check
  22. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); /admin/myprofile
  23. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); /admin/myprofile
  24. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); Encoder Factory /admin/myprofile
  25. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); Encoder Factory $factory-­‐>getEncoder($user); /admin/myprofile
  26. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); Encoder Factory $factory-­‐>getEncoder($user); $encoder /admin/myprofile
  27. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); Encoder Factory $factory-­‐>getEncoder($user); $encoder Encoder /admin/myprofile
  28. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); Encoder Factory $factory-­‐>getEncoder($user); $encoder Encoder $encoder-­‐>isPasswordValid('mypwd'); /admin/myprofile
  29. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); Encoder Factory $factory-­‐>getEncoder($user); $encoder Encoder $encoder-­‐>isPasswordValid('mypwd'); true /admin/myprofile
  30. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); Encoder Factory $factory-­‐>getEncoder($user); $encoder Encoder $encoder-­‐>isPasswordValid('mypwd'); true authenticated /admin/myprofile
  31. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); Encoder Factory $factory-­‐>getEncoder($user); $encoder Encoder $encoder-­‐>isPasswordValid('mypwd'); true authenticated /admin/myprofile or
  32. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Provider $provider-­‐>loadUserByUsername('sarah.khalil'); $user  =  new  User(‘sarah.khalil’); Encoder Factory $factory-­‐>getEncoder($user); $encoder Encoder $encoder-­‐>isPasswordValid('mypwd'); true authenticated false | 401: Unauthorized /admin/myprofile or
  33. $this-­‐>denyAccessUnlessGranted('ROLE_ADMIN',  null,  'Unable  to  access  this  page!');   $decision  =

     $this-­‐>get('security.authorization_checker')-­‐>isGranted('ROLE_ADMIN');   if  (false  ===  $decision)  {          throw  $this-­‐>createAccessDeniedException('Unable  to  access  this  page!');   } What you have usually in a controller or
  34. Or in a template {%  if  is_granted('ROLE_ADMIN')  %}    

         <a  href="...">Delete</a>   {%  endif  %}
  35. Or in a template {%  if  is_granted('ROLE_ADMIN')  %}    

         <a  href="...">Delete</a>   {%  endif  %} Symfony\Bridge\Twig\Extension\SecurityExtension
  36.        final  public  function  isGranted($attributes,  $object  =  null)

             {                  //  Checks  if  there  is  someone  authenticated  (if  there  is  a  token)   //  Makes  sure  that  the  attributes  in  an  array   //  returns  true  or  false                  return  $this-­‐>accessDecisionManager-­‐>decide($token,  $attributes,  $object);          } Symfony\Component\Security\Core\Authorization \AccessDecisionManagerInterface Any string that represent a permission anything that can help to make a decision
  37. VOTERS Voters are added in a compiler pass (Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass )

    The services must have a « security.voter » tag to be added in the AccessDecisionManager.
  38. VOTERS Voters are added in a compiler pass (Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass )

    The services must have a « security.voter » tag to be added in the AccessDecisionManager. All the voters are sorted by the attribute priority (the higher one is the last to be executed). If you don’t put any priority, => 0 (first to be executed) Why? To make sure that is doesn’t mess with other decisions taken
  39. $strategy, $allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions //  Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension   $container      

       -­‐>getDefinition('security.access.decision_manager')          -­‐>addArgument($config['access_decision_manager']['strategy'])          -­‐>addArgument(                    $config['access_decision_manager']['allow_if_all_abstain']          )          -­‐>addArgument(                    $config['access_decision_manager']['allow_if_equal_granted_denied']          )   ;
  40. //Symfony\Component\Security\Core\Authorization\AccessDecisionManager   public  function  decide(TokenInterface  $token,  array  $attributes,  $object  =

     null)   {            return   $this-­‐>{$this-­‐>strategy} ($token,  $attributes,  $object);   } decideAffirmative decideConsensus decideUnanimous
  41. decideAffirmative • Donne accès si n’importe lequel des voter dit

    ACCES_GRANTED. • Si tous les voters disent ACCES_ABSTAIN => $allowIfAllAbstainDecision prend la décision. One says yes. default = false
  42. decideConsensus • Donne accès si la majorité (sans les abstentions)

    dit ACCES_GRANTED. • S’il y a autant de ACCES_GRANTED que d’ ACCES_DENIED => $allowIfEqualGrantedDeniedDecision prend la décision • Si tous les voters disent ACCES_ABSTAIN => $allowIfAllAbstainDecision prend la décision. Most of them says yes. default = true
  43. decideUnanimous • Donne accès si tous les voters (sans les

    abstentions) disent ACCES_GRANTED. • Si tous les voters disent ACCES_ABSTAIN => $allowIfAllAbstainDecision prend la décision. All of them says yes. default = false
  44. decideXxxx //Symfony\Component\Security\Core\Authorization\AccessDecisionManager   //…   foreach  ($this-­‐>voters  as  $voter)  {

             $result  =  $voter-­‐>vote($token,  $object,  $attributes);   }   //  logic  to  return  true  or  false   200 or 403 (forbidden)
  45. Controller or template or service from AuthorizationChecker::isGranted() Strategy Voters Decision

    maker if all can’t make a decision Decision maker if all abstain needs AccessDecisionManager::decide()
  46. Controller or template or service from AuthorizationChecker::isGranted() AccessDecisionManager::decideAffirmative() or AccessDecisionManager::decideConsensus()

    or AccessDecisionManager::decideUnanimous() Strategy Voters Decision maker if all can’t make a decision Decision maker if all abstain needs AccessDecisionManager::decide()
  47. Controller or template or service from VoterInterface::vote() … VoterInterface::vote() calls

    AuthorizationChecker::isGranted() AccessDecisionManager::decideAffirmative() or AccessDecisionManager::decideConsensus() or AccessDecisionManager::decideUnanimous() Strategy Voters Decision maker if all can’t make a decision Decision maker if all abstain needs AccessDecisionManager::decide()
  48. Controller or template or service from VoterInterface::vote() … VoterInterface::vote() calls

    AuthorizationChecker::isGranted() AccessDecisionManager::decideAffirmative() or AccessDecisionManager::decideConsensus() or AccessDecisionManager::decideUnanimous() false true returns Strategy Voters Decision maker if all can’t make a decision Decision maker if all abstain needs AccessDecisionManager::decide()
  49. Previously… with the SimplePreauthenticator (1/4) • Implement the SimplePreAuthenticatorInterface: 1.createToken(Request

    $request, $providerKey) 2.authenticateToken(TokenInterface $token, UserProviderInterface $userProvider) 3.supportsToken(TokenInterface $token, $provider)
  50. Previously… with the SimplePreauthenticator (1/4) • Implement the SimplePreAuthenticatorInterface: 1.createToken(Request

    $request, $providerKey) 2.authenticateToken(TokenInterface $token, UserProviderInterface $userProvider) 3.supportsToken(TokenInterface $token, $provider) declare it as a service (github_authenticator)
  51. Previously… with the SimplePreauthenticator (2/4) • Implement the UserProviderInterface: 1.loadUserByUsername($username)

    2.refreshUser(UserInterface $user) 3.supportsToken($class) declare it as a service (github_authenticator)
  52. Previously… with the SimplePreauthenticator (4/4) security: firewalls: secured_area: pattern: ^/admin

    stateless: false simple_preauth: authenticator: github_authenticator provider: github_user_provider logout: path: /admin/logout target: /
  53. Implement the GuardAuthenticatorInterface 1.start(Request $request, AuthenticationException $exception) 2.getCredentials(Request $request) 3.getUser($credentials,

    UserProviderInterface $userProvider) 4.checkCredentials($credentials, UserInterface $user) 5.onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) 6.onAuthenticationFailure(Request $request, AuthenticationException $exception) 7.supportsRememberMe()
  54. • When to create a firewall (different way of authenticating)

    • Voters must be light in case of unanimous or consensus (because of the fact that all voters are executed)