===== Authentification pour les api rest ===== ==== Rappel ==== * **stateless** : la session http n'est pas maintenue entre les appels * **statefull** : la session http est maintenue entre les appels (on peut y stocker les informations de l'utilisateur) ==== Principe général ==== Dans tous les cas, le principe est qu'à chaque appel au backend : - on identifie le user de l'appel - on vérifie qu'il a les droits d'appeller la méthode appelée Généralement, les api rest sont stateless, que ce soit avec ou sans protection. (Le mode statefull introduit des risques de sécurité, qu'il faut contrer en ajouter d'autres mécanismes comme le CSRF). ==== Mise en oeuvre possible d'authentifications pour des service rest ==== === 1.Authentication Basic === * login et password encodé en base64, puis envoyé à chaque requete http, souvent dans le header Authorization * Le backend, à chaque appel, vérifie donc le username et le password. * Ici, le backend est généralement stateless. Si on le souhaite statefull, mais dans ce cas, il doit conserver la protection **Authorization : Basic ** * Exemples : - [[https://www.baeldung.com/spring-security-basic-authentication]] - [[https://www.sedooe.com/2016/04/rest-authentication-using-spring-security-and-spring-session/]] - [[http://websystique.com/spring-security/secure-spring-rest-api-using-basic-authentication/]] Les solutions qui suivent sont toutes à base de token (jetons) générés et mémorisés par le backend. En général, ces tokens sont uniques, et ne sont pas partagés entre les clients, chaque token représentant justement la session d'un client. Le client envoie ce token dans un header http (2 premiers exemples), ou dans le header http des cookies (la session est automatiquement maintenue par le navigateur) === 2.Authentication Oauth compliant === * l'utilisateur s'authentifie par une requete backend avec login et password pour obtenir un token, comme par exemple JWT * Il insère ensuite ce token dans le header Authorization pour toute les requêtes suivante. Authorization : Bearer * Le backend est donc chargé, à chaque requête (hors authent), de lire le token, de le valider, * Ici, le backend est généralement stateless. Si on le souhaite statefull, mais la protection doit être conservée. * Exemple : - Mon projet jobmanager - le projet orange-bank (plus complexe, définit une double authentification) - le projet rte naza-emulator-back - https://octoperf.com/blog/2018/03/08/securing-rest-api-spring-security/ : bon exemple et bon tuto Remarque : OAuth 2.0 est une norme complète, qui définit exactement comment les échanges doivent se passer, quelles sont les structures de données, etc ... https://oauth.net/2/ === 3.Authentication avec Token customisé === * __Exemple 1__ : https://gist.github.com/thomasdarimont/8d6bc243d3b504439e67d57cb0d0bb72 :\\ Exemple qui définit le minimum de conf pour faire de l'authentification avec un jeton situé dans le header http : **x-auth-token**.\\ Pour cela, il utilise la classe : **org.springframework.session.web.http.HeaderHttpSessionStrategy** (voir javadoc) pour instancier la classe de configuration HttpSessionConfig.\\ Cette dernière est décorée avec @Configuration et **@EnableSpringHttpSession** ==> Utilise le concept de session http wrappé par le framework spring. __Remarque trouvée sur un forum : __\\ Authorization is the primary header used by clients to authenticate against peers in HTTP as foreseen in RFC 7235.\\ Through the IANA HTTP Authentication Scheme Registry (see also: RFC 7235, sec. 5.1) you will find the Bearer scheme (defined in RFC 6750), which is closely tied to OAuth 2.0.\\ X-Auth-Token is pretty much providing a shortcut here as it (presumably) does not rely on either OAuth or the HTTP authentication framework.\\ Please note that with X-Auth-Token being an unregistered header, it is subject to no formal specification and its presence and content is always tied to a respective application. No general assumptions can be made on it. * __Exemple 2__ : https://howtodoinjava.com/spring-restful/custom-token-auth-example/ : montre juste comment se servir d'un header http défini pour cette appli ( AUTH_API_KEY ) pour authoriser ou non les requetes.\\ Dans cet exemple, il y a tout le reste à mettre en place : lecture/serialisation du token, chargement de l'utilisateur pour le mettre dans * __Exemple 3__ : https://kariera.future-processing.pl/blog/exploring-spring-boot-and-spring-security-custom-token-based-authentication-of-rest-services-with-spring-security-and-pinch-of-spring-java-configuration-and-spring-integration-testing/ === 4.Authentication Authent + statefull + CSRF === * Le user s'authentifie (avec un formulaire de login ou autre méthode). Sa session est stockée coté backend par HttpSession. * Ici, on est donc en statefull. * La session est réindiquée par le front via le cookie JESSIONID ; parfois l'échange doit se faire via l'url de ce type : http:///.../?...&JESSIONID= * La session porte alors le user authentifié. * Pour des raisons évidentes de sécurité, une protection supplémentaire est nécessaire. On voit souvent du CSRF. * Exemple : mon appli qcmonline === 5. Autres mécanismes === Pour être complet sur les mécanismes d'authentification envisageables : http://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml === 6. Remarques === A priori, csrf n'est pas nécessaire quand on utilise un token dans Authorization, parce que le navigateur n'authentifie pas automatiquement la requête ( comme c'est le cas avec le cookie JESSIONID de java ). https://security.stackexchange.com/questions/170388/do-i-need-csrf-token-if-im-using-bearer-jwt === 7.Notion de refresh_token === Bien souvent, les tokens générés par les backend ont une date d'expiration ; OAuth 2.0 définit un mécanisme, qui est le suivant : Quand le backend renvoie un token suite à un login/password successfull, il renvoie avec ce token, un refresh_token ( https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/ ) Cela permet au frontend de renouveler son token quand il voit qu'il arrive à expiration (sans avoir besoin d'une intervention de l'utilisateur). L'implémentation doit suivre ceci : https://www.oauth.com/oauth2-servers/access-tokens/refreshing-access-tokens/ Autre lien : Authentification pour les websocket : https://github.com/spring-projects/spring-framework/blob/master/src/docs/asciidoc/web/websocket.adoc#token-authentication ===== Spring security ===== ==== Authentication ==== org.springframework.security.core.Authentication est un objet spring qui peut représenter tantot les credentials pour la 1ere étape d'authentification, ou bien le jeton passé à chaque requete http.\\ Ci-dessous je ne présente que la partie où il sert de jeton. UsernamePasswordAuthenticationToken hérite de Authentication. \\ Cet objet est peuplé par une classe de filtre, située vers le début de la chaine de traitement de chaque requetes http ; puis cette classe appelle AuthenticationManager.authenticate(Authentication) AuthenticationManager.authenticate(Authentication) Globalement, la session HTTP porte un objet Authentication, qui permet de savoir si l'utilisateur est authentifié, et connaitre le détail de l'utilisateur. The AuthenticationManager is just an interface, so the implementation can be anything we choose The default implementation in Spring Security is called 'ProviderManager' and rather than handling the authentication request itself, it delegates to a list of configured AuthenticationProvider s, each of which is queried in turn to see if it can perform the authentication. Each provider will either throw an exception or return a fully populated Authentication object. Identification à chaque requete via header bearer NazaTokenAuthenticationChainFilter.doFilterInternal() --> ProviderManager.authenticate(extractedToken) --> NazaTokenAuthenticationProvider(implement AuthenticationProvider).authenticate(extractedToken) --> NazaTokenAuthenticationProvider.retrieveUser(extractedToken) --> ... on retrouve le user ou on envoie une AuthenticationException ... Si aucune exception, alors à un moment dans ProviderManager, un code proche du suivant est appelé pour setter le user du thread exécutant la requete http : SecurityContextHolder.getContext().setAuthentication(authentication); Chaque AuthenticationProvider.authenticate(Authentication) peut lancer une exception AuthenticationException qui est la classe abstraite de BadCredentialsException, AccountExpiredException, en gros toute exception qui invalide l'autorisation). ==== Droits d'accès ==== @Secured("ROLE_USER1") @RequestMapping(value = "/test-with-role-user1-secured", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public void testWithRole1Secured() { ... } @Secured("ROLE_USER2") @RequestMapping(value = "/test-with-role-user2-secured", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public void testWithRole2Secured() { ... } @PreAuthorize("hasRole('USER1')") public void testWithRole1PreAuthorize() { ... }