Sécurisez vos applications Angular avec Spring Security et HMAC
Dans cet article nous allons voir comment implémenter HMAC sur une communication REST. L’architecture applicative proposée est constituée d’un frontend développé en Angular 2 et d’un backend Spring et Spring Security.
L’environnement technique
Stack
Les composants techniques sont les suivants :
Partie serveur:
- Spring 4.3.9
- Spring Boot 1.5.4
- Spring Security 4.2.3
- Base de données H2 (in-memory)
Partie cliente:
- Angularv 4.1.2
- HTML 5 et CSS 3
- Librairie JavaScript CryptoJS pour l’encodage.
La communication est faite via des webservices REST entre la partie front office (AngularJS) et la partie serveur (Spring) notamment grâce à Spring MVC.
Sources
Vous pouvez récuperer les sources du projet d’exemple sur notre github : Angular Spring HMAC
Installation
- Pour lancer le serveur : mvn spring-boot:run puis ouvrir un navigateur à l’adresse : http://localhost:8080. La commande npm install est faite automatiquement au lancement du serveur.
- Les différents comptes utilisateurs sont dans le fichier README.md
Définition
MAC
MAC, de l’anglais message authentication code, est un code accompagnant les données permettant de vérifier que ces dernières n’ont pas été modifiées pendant la transmission. Le principe repose sur la génération d’un bloc authentificateur à l’aide d’un algorithme prédéfini et d’une clé secrète.
HMAC
HMAC, de l’anglais keyed-hash message authentication code, repose sur le principe d’un MAC mais ajoute le chiffrement des données à partir d’un algorithme de hashage de type SHA, MD5, etc… toujours en combinaison avec une clé secrète.
Intérêt
HMAC permet d’éviter les attaques de type « man in the middle » où un intrus se place entre l’émetteur et le récepteur. Il se fait alors passer pour le récepteur auprès de l’émetteur tout en continuant à transmettre les données de manière transparente. Il peut alors potentiellement lire et/ou modifier les données et tromper alors l’emetteur. La vérification des données par le récepteur assure alors que ces données n’ont pas été altérées. En effet, l’intrus ne connaissant pas la clé secrète ni l’algorithme utilisé pour produire la clé MAC il lui est impossible d’altérer les données.
HMAC permet donc de s’assurer de l’intégrité des données et de l’authenticité de l’emetteur. Le niveau de sécurité et d’efficacité d’HMAC est exponentielle avec la longeur de la clé secrète et la force de l’algorithme de hashage utilisé. Par exemple, une clé secrète « redfroggy » et un chiffrement MD5 sera moins efficace qu’une clé comme « 03742b9c204cf3b31b3f2a20f55e5ca9ddd7ef1f15e3297c56d5b9d8a3c6d0d3 » et d’un chiffrement SHA-256.
Une fois le chiffrement effectué, on parle alors d’algorithmes HMAC-SHA256, HMAC-SHA1, HMAC-MD5, etc …
Il est important de rappeler que la mise en place de HMAC n’a de sens que si la communication entre le client et le serveur est chiffrée notamment via https.
Principe
Comme on peut le voir sur le schéma ci dessus, le principe est simple : une requête est envoyée du client au serveur et l’on souhaite vérifier que son contenu n’a pas été altéré.
Coté client
- Le client va constituer un message (basé sur un algorithme partagé entre le client et le serveur) puis l’encoder en utilisant un algorithme de hashage (SHA-256, MD5, etc..) et une clé secrète partagée.
- La donné finale est envoyée au serveur (par exemple dans un header HTTP) : C’est la clé A sur notre schéma.
Coté serveur
- Le serveur applique le même algorithme que le client pour constituer son message, puis applique la même fonction de hashage en utilisant la clé secrète partagée. Il en ressort la clé B.
- Le serveur va alors comparer la clé B avec la clé A : Si elles sont identiques alors la requête est valide et le message n’a pas été altéré.
Json Web Token
L’implémentation de HMAC que nous avons mis en place et qui est proposée dans cet article fait appel à une autre notion : Le Json Web Token (JWT).
Ils permettent de mettre facilement et de manière securisée une authentification sur des applications de type stateless c’est à dire qui ne stocke aucune session coté serveur. C’est ce qu’on appelle une authentification basée sur un token (Token based authentication)
Le web token est généralement un hash transmis au moment de l’authentification et qui va transiter dans toutes les requêtes entre le client et le serveur. Il est préférable de générer un JWT à chaque requête pour éviter les attaques de type CSRF, c’est à dire ou un intrus utilise le token client pour dialoguer avec le serveur. Si le token change à chaque requête cela rend sa tâche beaucoup plus difficile.
Comme exemple on peu cité le SSO (Single Sign On) qui repose sur le principe du JWT. La grande force d’un JWT est qu’il n’est stocké nulle part sur le serveur mais simplement échangé avec le client (seul le secret doit être stocké ou défini en fichier de configuration coté serveur). Il convient donc particulièrement bien pour les applications de type stateless.
Dans l’exemple que nous verrons par la suite, l’authentification repose sur un JWT, et le processus peut être schématisé de la façon suivante :
Le processus est le suivant:
- Le client fait une requête d’authentification
- Le serveur authentifie l’utilisateur sur un tiers (base de données ou ldap par exemple)
- En cas de succès, le serveur génère un secret (basé sur un UUID) qu’il stocke en cache ou utilise un secret qui peut être défini en fichier de configuration ce qui est préférable car persister un secret coté serveur reviendrait à définir une session.
- Le serveur génère ensuite un JWT qui sera envoyé au client en réponse. Il contient notamment l’identifiant de l’utilisateur fraîchement authentifié ce qui nous permettra à chaque requête de pouvoir authentifié auprès de Spring Security l’utilisateur.
- Le JWT est renvoyé au client sous forme d’un cookie avec la propriété httpOnly = true (seul le navigateur à accès au cookie) ce qui empêche la lecture du cookie notamment via JavaScript.
- A aucun moment le secret qui permet de signer le JWT est envoyé au client, seul celui utilisé pour la signature des requêtes HMAC est stocké et utilisé par le client.
Les informations sont échangées grâce aux headers HTTP car c’est un moyen efficace de transmettre de la donnée sans altérer le contenu du message de la requête HTTP. Les headers HTTP utilisés sont les suivants:
- WWW-Authenticate : Contient la méthode de chiffrement à utiliser pour chiffre les prochaines requêtes (Ex: HMACSHA256, HMACSHA512, etc)
- X-Secret : Contient la clé secrète à utiliser pour signer les prochaines requêtes HMAC.
En plus de ces headers, un cookie est renvoyé au client à l’authentification et contient le JWT généré permettant d’identifier un utilisateur pour une requête HTTP.
Dans notre stack d’exemple, l’authentification est réalisée grâce à Spring Security. Pour plus d’informations voir l’article sur l’architecture Spring Security.
JWT, cookies et attaques CSRF
L’avantage de stocker le JWT en cookie réside dans le fait que sa transmission est transparente (tant que les requêtes se font bien sur le même domaine) et qu’il est possible de limiter l’exposition du cookie (avec les propriétés httpOnly ou secured) et donc de rendre l’information (dans notre cas le JWT) moins vulnérable:
- Un cookie marqué comme httpOnly n’est accessible que par le navigateur et pas par JavaScript
- Un cookie marqué comme secured sera stocké par le navigateur uniquement si le client utilise le protocole https.
Le revers de la médaille est qu’un cookie est vulnérable à une attaque de type CSRF où un script malicieux réutiliserait le cookie pour modifier de la donnée coté serveur. La documentation Spring explique assez bien ce principe.
Avec Spring Security, il est possible de générer un token pour chaque client qui sera stocké en cookie et transmis en header http. Si le header ne correspond pas au cookie une erreur est retournée par le serveur. Voir SecurityConfiguration.
Code Time !
Pour récuperer les sources du projet, faite un git clone sur notre projet Angular Spring HMAC. Il s’agit d’une simple application Angular/Spring qui se contente d’afficher une liste d’utilisateurs après une authentification obligatoire. La liste des utilisateurs est crée au lancement du serveur dans une base de données H2.
L’authentification
Coté serveur
On déclare tout d’abord une api rest pour l’authentification grâce à Spring MVC :
1 2 3 4 5 6 7 8 9 10 |
@RestController @RequestMapping(value = "/api") public class Authentication { @Autowired private AuthenticationService authenticationService; @RequestMapping(value = "/authenticate",method = RequestMethod.POST) public UserDTO authenticate(@RequestBody LoginDTO loginDTO, HttpServletResponse response) throws Exception{ return authenticationService.authenticate(loginDTO,response); } } |
Le webservice est de type POST et les credentials sont représentés par l’objet LoginDTO qui contient un identifiant et un mot de passe.
La logique de l’authentification est encapsulée dans un service AuthenticationService :
1 |
public UserDTO authenticate(LoginDTO loginDTO, HttpServletResponse response) throws HmacException {} |
Dans un premier temps on réalise l’authentification via Spring Security:
1 2 3 4 5 |
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginDTO.getLogin(),loginDTO.getPassword()); Authentication authentication = authenticationManager.authenticate(authenticationToken); SecurityContextHolder.getContext().setAuthentication(authentication); //Retrieve security user after authentication SecurityUser securityUser = (SecurityUser) userDetailsService.loadUserByUsername(loginDTO.getLogin()); |
- L’instance de l’authenticationManager est récupérée grâce à un @Autowired et permet d’appeler la méthode authenticate qui déclence les mécanismes d’authentification de Spring Security.
- On place ensuite le résultat dans le context Spring Security et on charge les données de l’utilisateur par son identifiant grâce a la classe UserDetailsService.
- Pour bien comprendre les mécanismes d’authenfication de Spring Security, je vous renvoie vers cet article.
- On génère ensuite un secret grâce à la classe utilitaire HmacSigner :
1 |
String secret = HmacSigner.generateSecret(); |
- On génère un JWT toujours grâce à la classe HmacSigner :
1 |
HmacToken hmacToken = HmacSigner.getSignedToken(secret,String.valueOf(securityUser.getId()),customClaims); |
La classe HmacToken regroupe un ensemble d’élément nécessaire à la composition du JsonWebToken mais surtout le JWT lui même.
- On crée le cookie qui contient le JWT généré:
1 2 3 4 5 6 |
// Add jwt as a cookie Cookie jwtCookie = new Cookie(ACCESS_TOKEN_COOKIE,hmacToken.getJwt()); jwtCookie.setPath(request.getContextPath().length() > 0 ? request.getContextPath() : "/"); jwtCookie.setMaxAge(securityProperties.getJwt().getMaxAge()); //Cookie cannot be accessed via JavaScript jwtCookie.setHttpOnly(true); |
- Enfin, on retourne les informations nécessaires dans les headers HTTP:
1 2 |
response.setHeader(HmacUtils.X_SECRET, hmacToken.getSecret()); response.setHeader(HttpHeaders.WWW_AUTHENTICATE, HmacUtils.HMAC_SHA_256); |
Coté client
Le service LoginService se charge de l’appel au backend
-
123456789authenticate(username:string,password:string):Observable<Account> {let headers = new Headers();headers.append('Content-Type', 'application/json');return this.http.post(AppUtils.BACKEND_API_ROOT_URL+AppUtils.BACKEND_API_AUTHENTICATE_PATH, JSON.stringify({login:username,password:password}),{headers:headers}).map((res:Response) => {(....)return account;});}
C’est le service LoginService, au retour de l’authentification qui va lire les headers HTTP. Les données sont stockées en local storage.
-
123456789101112131415161718192021222324authenticate(username:string,password:string, rememberMe: boolean):Observable<Account> {let headers = new Headers();headers.append('Content-Type', 'application/json');return this.http.post(AppUtils.BACKEND_API_ROOT_URL+AppUtils.BACKEND_API_AUTHENTICATE_PATH,JSON.stringify({login:username,password:password}),{headers:headers}).map((res:Response) => {let securityToken:SecurityToken = new SecurityToken({secret:res.headers.get(AppUtils.HEADER_X_SECRET),securityLevel:res.headers.get(AppUtils.HEADER_WWW_AUTHENTICATE)});if(rememberMe) {localStorage.setItem(AppUtils.STORAGE_ACCOUNT_TOKEN, res.text());localStorage.setItem(AppUtils.STORAGE_SECURITY_TOKEN, JSON.stringify(securityToken));} else {sessionStorage.setItem(AppUtils.STORAGE_ACCOUNT_TOKEN, res.text());sessionStorage.setItem(AppUtils.STORAGE_SECURITY_TOKEN, JSON.stringify(securityToken));}let account:Account = new Account(res.json());this.sendLoginSuccess(account);return account;})}
Coté http, voici un exemple de headers retournés lors se l’authentification:
1 2 3 |
WWW-Authenticate:HmacSHA256 X-Secret:M2YzMTZlN2YtOGFlZC00ZjUxLWJjY2UtNDIzZDYzODQ2ZWY3 Set-Cookie:access_token=eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NTYxNDg3NjYsImwtbGV2IjoiSG1hY1NIQTI1NiIsImlzcyI6IjEiLCJqdGkiOiIyZmY0NWNlMC03ZGVlLTRlN2QtOTlkNC0zNDI4YmE4MjhlNWQifQ.HsexOJPCDOyla4K61LMp00Q4XIHQ1CIgsTzd-f21GW4 |
Signature des requêtes api (hors authentification)
- Le client génère un message en concaténant l’url appelée, la méthode HTTP et la date de la requête. message = url + method + date
- Il génère ensuite un digest avec un encodage HMAC à partir du secret (récupéré du fichier de configuration) et du message. digest = HMACSHA256(secret,message)
- Le JWT est automatiquement transmis car stocké en cookie.
- Le digest est placé dans un header HTTP X-Digest
- La date utilisée pour générer le message est placée dans un header HTTP X-Once
- Le serveur vérifie ensuite la signature du JWT à l’aide du secret présent dans le fichier de configuration application.yml.
- Si le JWT est conforme, le serveur génère un message basé sur l’url, la méthode http et la date stockée dans le header X-Once
- Il génère ensuite un digest HMAC à partir du message et du secret.
- Si le digest serveur est strictement égal au digest client stocké dans le header X-Digest alors la requête est valide
Coté client
Angular nous permet, grâce aux intercepteurs http, d’appliquer une logique de manière globale pour toutes les requêtes http qui seront appelées. Les intercepteurs angular sont déclenchés pour tout types de requêtes HTTP : url api, ressource html, etc… Il nous faut donc filter les requêtes pour ne prendre que celle qui correspondent aux apis de notre backend.
On utilise une surcharge de la classe de base Http: HmacHttpClient
-
123456789@Injectable()export class HmacHttpClient extends Http {http:Http;loginService:LoginService;constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions,loginService:LoginService) {super(_backend,_defaultOptions);this.loginService = loginService;}}
La classe HmacHttpClient fait appel à la classe UrlMatcher pour déterminer quels apis sont valables.
-
123456export class UrlMatcher {public static matches(url:string):boolean {return url.indexOf(BACKEND_API_PATH) !== -1&& url.indexOf(BACKEND_API_PATH+BACKEND_API_AUTHENTICATE_PATH) === -1;}}123456789101112@Injectable()export class HmacHttpClient extends Http {http:Http;loginService:LoginService;constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions,loginService:LoginService) {super(_backend,_defaultOptions);this.loginService = loginService;}addSecurityHeader(url:string,method:string,options: RequestOptionsArgs):void {if(AppUtils.UrlMatcher.matches(url)) {}}}
Ainsi, seules les requêtes comprenant « /api/** » et qui ne contiennent pas « /api/authenticate » vont déclencher notre logique présente dans l’intercepteur.
- Si les urls correspondent, on génère tout d’abord un message
- On récupère le secret que l’on décode de la base 64
- La librairie angular-utf8-base64 est utilisé pour l’encodage base64
- On génère le message
-
123456if(AppUtils.UrlMatcher.matches(url)) {let securityToken:SecurityToken = new SecurityToken(JSON.parse(localStorage.getItem(AppUtils.STORAGE_SECURITY_TOKEN)));let date:string = new Date().toISOString();let secret:string = securityToken.secretKey;let message = method + url + date;}
- On génère le digest à partir du secret et du message. L’algorithme de hashage utilisé va dépendre du mode d’encodage renvoyé par le serveur.
- La librairie JavaScript CryptoJS est utilisé pour l’encodage HMAC.
-
1234567if (securityToken.isEncoding('HmacSHA256')) {options.headers.set(AppUtils.HEADER_X_DIGEST, CryptoJS.HmacSHA256(message, secret).toString());} else if (securityToken.isEncoding('HmacSHA1')) {options.headers.set(AppUtils.HEADER_X_DIGEST, CryptoJS.HmacSHA1(message, secret).toString());} else if (securityToken.isEncoding('HmacMD5')) {options.headers.set(AppUtils.HEADER_X_DIGEST, CryptoJS.HmacMD5(message, secret).toString());}
- On place les informations pertinentes dans les différents headers HTTP
-
12options.headers.set(AppUtils.HEADER_X_DIGEST, CryptoJS.HmacSHA256(message, secret).toString());options.headers.set(AppUtils.HEADER_X_ONCE, date);
Coté http, voici un exemple de headers envoyés par le client:
1 2 3 |
Cookie:access_token=eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NTYxNDg3NjYsImwtbGV2IjoiSG1hY1NIQTI1NiIsImlzcyI6IjEiLCJqdGkiOiIyZmY0NWNlMC03ZGVlLTRlN2QtOTlkNC0zNDI4YmE4MjhlNWQifQ.HsexOJPCDOyla4K61LMp00Q4XIHQ1CIgsTzd-f21GW4 X-Digest:ea3df87b22014e6a2942582700dcf960be3a10a7a875beb6636725c8cab178d8 X-Once:2016-02-22T13:46:01.135Z |
Côté serveur
Pour le backend, l’idée est de vérifier la validité de la requête HMAC avant que Spring Security effectue l’authentification.
Pour cela les filtres Spring Security vont nous permettre de répondre au besoin. Ce sont les classes XAuthTokenFilter, HmacSecurityFilter et SecurityService qui vont jouer ce rôle de vérification dans notre cas. Les filtres XAuthTokenFilter et HmacSecurityFilter sont exécutés dans cet ordre par Spring Security.
Le filtre HmacSecurityFilter est générique et peut donc être repris tel quel avec tout le package hmac security.
Vérification de l’authentification
Dans le filtre XAuthTokenFilter on vérifie que la requête courant possède bien un cookie contenant un JWT et que ce dernier est valide. Si ce n’est pas le cas une erreur 401 est retournée.
- Le filtre appel SecurityService
1 |
this.securityService.verifyJwt(request); |
- On vérifié la présence du cookie, la validité du JWT et en cas de succès au authentifie l’utilisateur courant auprès de Spring Security :
1 2 3 4 5 6 7 |
Cookie jwtCookie = WebUtils.getCookie(request, AuthenticationService.ACCESS_TOKEN_COOKIE); Assert.notNull(jwtCookie,"No jwt cookie found"); String jwt = jwtCookie.getValue(); String login = SecurityUtils.getJwtClaim(jwt, AuthenticationService.JWT_CLAIM_LOGIN); Assert.notNull(login,"No login found in JWT"); Assert.isTrue(SecurityUtils.verifyJWT(jwt, securityProperties.getJwt().getSecret()),"The Json Web Token is invalid"); Assert.isTrue(!SecurityUtils.isJwtExpired(jwt),"The Json Web Token is expired"); |
Dans le filtre HmacSecurityFilter on vérifie que la requête est signée correctement et qu’elle n’a pas été altérée:
- On applique la vérification uniquement pour les requêtes http dont l’url contiennent « /api/** » et ne contiennent pas « /api/authenticate ». C’est le même comportement que notre intercepteur angular.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class HmacSecurityFilter extends GenericFilterBean { private HmacRequester hmacRequester; public HmacSecurityFilter(HmacRequester hmacRequester) { this.hmacRequester = hmacRequester; } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; if (!request.getRequestURI().contains("/api") || request.getRequestURI().contains("/api/authenticate")) { filterChain.doFilter(wrappedRequest, response); } else { if(securityService.verifyHmac(wrappedRequest)) { filterChain.doFilter(wrappedRequest, response); } } } } |
- La classe SecurityService est appelée pour faire les vérifications où on vérifie que le JWT (envoyé par cookie) est valide :
1 2 3 4 5 6 |
Cookie jwtCookie = WebUtils.getCookie(request, AuthenticationService.ACCESS_TOKEN_COOKIE); Assert.notNull(jwtCookie,"No jwt cookie found"); String jwt = jwtCookie.getValue(); if (jwt == null || jwt.isEmpty()) { throw new HmacException("The JWT is missing from the '" + HmacUtils.AUTHENTICATION + "' header"); } |
- On génère le message en fonction du type de requête
1 2 3 4 5 6 |
String message; if ("POST".equals(request.getMethod()) || "PUT".equals(request.getMethod()) || "PATCH".equals(request.getMethod())) { message = request.getMethod().concat(request.getBody()).concat(url).concat(xOnceHeader); } else { message = request.getMethod().concat(url).concat(xOnceHeader); } |
- On génère le digest
1 |
<span class="pl-smi">String</span> digestServer <span class="pl-k">=</span> <span class="pl-smi">SecurityUtils</span><span class="pl-k">.</span>encodeMac(hmacSharedSecret, message, encoding); |
- Les digest sont comparés et s’il sont différents, la requête est interrompue et une erreur 403 est retournée.
1 2 3 4 5 6 7 |
if (digestClient.equals(digestServer)) { logger.debug("Request is valid, digest are matching"); return true; } else { logger.debug("Server message: " + message); throw new HmacException("Digest are not matching! Client: " + digestClient + " / Server: " + digestServer); } |
- En cas d’erreur, un code HTTP 403 « Unauthorized » est renvoyée au client.
1 2 3 4 5 |
catch(Exception e){ e.printStackTrace(); response.setStatus(403); response.getWriter().write(e.getMessage()); } |
Bonjour,
Merci pour le tutoriel, il est très claire et compréhensible, il m’a aidé beaucoup.
J’ai une question.
j’ai changé la classe « MockUsers » par une autre pour récupérer depuis la base de données et ça marche.
mais si j’ajoute un nouveau utilisateur au base de données et j’essaie d’utiliser ces nouveaux « login » et « password », ça marche pas.
le serveur affiche sur le console « »,
j’ai essayé de suivre l’exception avec le débogueur d’Eclipse et j’ai compris que le programme cherche dans la cache pour trouver les utilisateurs.
Est ce que j’ai raison ou non et comment remédier à ce problème?
Merci encore.
Bonjour,
L’erreur ne s’est pas affichée dans ton commentaire, je t’invite à reporter l’erreur dans github en levant une issue pour un suivi plus efficace.
Les utilisateurs sont effets stockés dans une simple liste statique et donc en mémoire.
Merci pour ton retour,
On vérifie dans la classe XAuthTokenFilter qu’un utilisateur existe bien pour l’identifiant transmit dans le jwt. Si tu as branché sur une vraie base de donnée il faut également changer cette ligne MockUsers.findById:
https://github.com/RedFroggy/angular-spring-hmac/blob/master/src/main/java/fr/redfroggy/hmac/configuration/security/XAuthTokenFilter.java#L47
et faire un appel à ta bdd.
Bonjour,
Votre article est vraiment super, j’essaie de l’implémenter mais je me posais une question.
Dans la méthode authenticate de la classe AuthenticationService. Vous dites ceci : ‘On stocke ensuite le secret de l’utilisateur authentifié de manière statique (normalement plutôt en cache) ‘
Pourriez-vous un peu expliciter cette phrase ? Ici si on utilise notre base de données, par quoi faudrait-il remplacer la boucle sur MockUsers pour donner le secret token au user et le garder en cache ? Dans le code suivant :
//Generate a random secret
String secret = HmacSigner.generateSecret();
HmacToken hmacToken = HmacSigner.getSignedToken(secret,String.valueOf(securityUser.getId()), HmacSecurityFilter.JWT_TTL,customClaims);
for(UserDTO userDTO : MockUsers.getUsers()){
if(userDTO.getId().equals(securityUser.getId())){
userDTO.setSecretKey(secret);
}
}
Merci pour votre aide !
Bonjour,
J’aurais une deuxième question, ajouter vous le code suivant dans le web.xml ? :
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
Merci pour votre aide.
Bonjour,
Dans un cas réel, à l’authentification, on génère le secret de l’utilisateur que l’on stocke en cache (via EHCache ou Hazelcast par exemple). La donné est alors stocké avec un système de clé valeur ou la clé peut etre l’identifiant de l’utilisateur et la valeur le secret. Ensuite, à chaque requête, lorsque l’on vérifie le token (dans la classe HmacSecurityFilter) on récupère le secret du cache pour vérifier la validité du token envoyé.
Un stockage en cache veut dire un stockage en mémoire ce qui est préférable car seul le code à accès à la donnée et donc le secret n’est pas vulnérable à une eventuelle faille de le BDD ( si quelqu’un à un accès non authorisé à la BDD il n’aura pas accès aux secrets générés).
Enfin un stockage en mémoire veut dire que à chaque redémarrage du serveur les secrets sont purgés ce qui oblige les utilisateurs à s’authentifier de nouveau ce qui est plutôt une bonne chose.
Salut,
Super article et implémentation, j’ai décidé de l’utiliser dans mon projet !
Mais je rencontre une erreur après que le user ait été authentifié, le router renvoit vers /users mais à ce moment là j’avais une erreur dans spring disant JWT is missing etc. En regardant dans la request du client, il n’y avait pas de token etc dans le header, donc dans le fichier user.service.ts j’ai remplacé l’objet http injecté par le type HmacHttpClient qui fournit le token dans la request. L’erreur suivante apparait alors lorsqu’on recommence l’authentification :
zone.js:461 Unhandled Promise rejection: EXCEPTION: Error in :0:0
ORIGINAL EXCEPTION: No provider for ConnectionBackend! (HmacHttpClient -> ConnectionBackend)
ORIGINAL STACKTRACE:
Error: DI Exception
at NoProviderError.BaseException [as constructor] (http://localhost:3000/node_modules/@angular/core/core.umd.js:3776:27)
at NoProviderError.AbstractProviderError [as constructor] (http://localhost:3000/node_modules/@angular/core/core.umd.js:4307:20)
at new NoProviderError (http://localhost:3000/node_modules/@angular/core/core.umd.js:4342:20)
at ReflectiveInjector_._throwOrNull (http://localhost:3000/node_modules/@angular/core/core.umd.js:5794:23)
at ReflectiveInjector_._getByKeyDefault (http://localhost:3000/node_modules/@angular/core/core.umd.js:5822:29)
at ReflectiveInjector_._getByKey (http://localhost:3000/node_modules/@angular/core/core.umd.js:5785:29)
at ReflectiveInjector_._getByReflectiveDependency (http://localhost:3000/node_modules/@angular/core/core.umd.js:5775:25)
at ReflectiveInjector_._instantiate (http://localhost:3000/node_modules/@angular/core/core.umd.js:5672:40)
at ReflectiveInjector_._instantiateProvider (http://localhost:3000/node_modules/@angular/core/core.umd.js:5644:29)
at ReflectiveInjector_._new (http://localhost:3000/node_modules/@angular/core/core.umd.js:5633:25)
ERROR CONTEXT:
Voilà peut-être sauras-tu m’aider ?
Merci !
Je ne reproduis pas le pb de mon coté il faudrait essayer de purger le local storage du navigateur car effectivement le jwt n’est pas censé manqué entre l’authentification et la liste des users.
Pour ce qui est de la classe HmacHttpClient dans user.service.ts, cette dernière est déja utilisée implicitement grâce au code ajouté dans le fichier main.ts où on précise que chaque injection de dépendance sur Http fait appel à l’instance de HmacHttpClient. il n’y a donc pas besoin de remplacer Http par HmacHttpClient.
Bonjour,
J’ai pu avancé dans l’intégration de votre projet dans mon programme.
Désormais, l’authentification se passe bien au login mais ensuite lorsqu’il y a le redirect vers /api/users, la requête qui est récupérée du côté serveur ne contient pas dans le header les données (comme ‘Authentification’ où se trouve le token).
Cependant, en debuggant côté client je vois que le header et toutes les données sont bien associées dans la requête. Je me dis que le problème se situe peut-être au niveau du CORS (pour l’authentification, déjà côté serveur j’avais dû adapter).
Auriez-vous une idée d’une configuration que je devrais ajouter au niveau du web-context.xml ou autre, ou une piste où chercher le problème ?
Merci pour votre aide !
Effectivement si vous avez une erreur de type cross domain cela peut jouer. Je vous invite à lire notre article sur le cross domain: http://www.redfroggy.fr/spring-rest-cross-domain/
Ajouter l’annotation @CrossOrigin devrait résoudre ce problème.
Bonjour ,
j’essaie d’intégrer ton projet dans un projet Spring MVC , mais j’arrive pas a avoir le secret et le message :
j’ai ca cote client .
secret null
hmac-http-client.ts:45
hmac message null
et il n y ‘ as de log cote seveur .
est ce que tu as une idée ?
thank you for the good tutorial,
I have a client (angular 2) outside server (Spring boot), I have extended the class MvcConfiguration with the method :
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping(« /api/** »);
}
my problem currently is that jwt is always null
————————————————————————————————————————————————————————
String jwt = request.getHeader(HmacUtils.AUTHENTICATION);
if (jwt == null || jwt.isEmpty()) {
throw new HmacException(« The JWT is missing from the ‘ » + HmacUtils.AUTHENTICATION + « ‘ header »);
}
————————————————————————————————————————————————————————-
I have seen in browser local storage, unfortunately, the field is not there.
What am I doing wrong?
Tout d’abord merci pour ce tutoriel complet, j’ai quelques petites questions si vous pouvez me répondre ça sera vraiment très apprécié.
Donc voilà supposons maintenant que quelqu’un arrive à intercepter le Secret et les données envoyées en claire dans les Headers pour la première étape d’authentification, est ce que cela aura un effet sur le compromis de la sécurité de l’application ?
Ensuite est ce que le stockage du Secret et du JWT coté client ne permet pas de les avoir facilement par exemple a l’aide d’une attaque XSS ?
J’espère vraiment une réponse de votre part, Merci.
Bonjour,
Merci pour ce très beau tuto et surtout complet et enrichissant de méthode.
J’ai essayé d’implémenter le projet en séparant coté serveur et client.
Cependant je m’en rend compte que le CORS ne permet pas au niveau java d’avoir accès au cookies.
Quelqu’un a t-il pu rencontré ce problème ?
Si oui je voudrais savoir votre solution.
Merci d’avance !
I have recently started a site, the info you provide on this website has helped me tremendously. Thanks for all of your time & work. The achievements of an organization are the results of the combined effort of each individual. by Vince Lombardi. ekedccfdkdef
I am not sure where you are getting your information,
but great topic. I needs to spend some time learning much
more or understanding more. Thanks for wonderful info I was looking for this info for my
mission.
Manchester United kläder