Spring security génÚre une page d'authentification (en communiquant le mot de passe en console). Ensuite, il cntourne la faille de sécurité CSRF en mettant en place le jeton de sécurité (d'ou la raison pour laquelle la page est bloquée).
Pour avoir la main sur la configuration (dans le cas ou on utilise une stratégie stateless),
on créé un fichier SecurityConfiguration pour personnaliser les paramÚtres de la méthode configure() :
.permitAll() Pour authoriser toutes URL
.csrf(...) Pour désactiver le csrf
.header(...) Dans lequel se trouve frameOption, qu'il faut désactiver aussi (dans le cas de l'utilisation de H2 DB)
.formLogin() GénÚre un formulaire d'authentification
Lâinterface UserDetailsService est le point dâentrĂ©e standard que Spring Security utilise pour rĂ©cupĂ©rer les informations dâun utilisateur (nom, mot de passe, rĂŽles, etc.) lors de lâauthentification.
Spring Security appelle cette méthode pour :
-
Trouver lâutilisateur dans ta base de donnĂ©es (ou autre source).
-
Retourner un objet UserDetails contenant les infos nĂ©cessaires Ă lâauthentification.
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;Conformément au diagram, pour implémenter JWT, on va devoir manipuler 2 méthodes importantes (qu'il va falloir surcharger) :
attemptAuthenticationLorsque l'utilisateur tente de s'authentifiersuccessfulAuthenticationUne foi l'utilisateur authentifié
Pour cela, on créé une class qui hérite de la class UsernamePasswordAuthenticationFilter :
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
}
}Dans successfulAuthentication on génÚre le jeton jwt :
User user = (User) authResult.getPrincipal();
Algorithm algorithm = Algorithm.HMAC256("mySecret1234");
String jwtAccessToken = JWT.create()
.withSubject(user.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 5 * 60 * 1000))
.withIssuer(request.getRequestURL().toString())
.withClaim("roles", user.getAuthorities().stream().map(ga -> ga.toString()).collect(Collectors.toList()))
.sign(algorithm);
response.setHeader("Authorization", jwtAccessToken);Pour tester :
Methode : POST, URL : {url}/login,
avec un body au format x-www-form-urlencoded.
En retour, dans le header de la réponse, on a un JWT dans le champ Authorization.
ProblĂšme : Une fois que l'on gĂ©nĂšre un token qui expire dans 30 jours, rien n'empĂȘche l'utilisateur d'accĂ©der Ă l'application durant toute cette pĂ©riode.
Solution : Utilisation de 2 token Access Token & Refresh token :
-
Durée de vie courte : souvent entre 15 minutes et 1 heure.
-
Contient les permissions (scopes) : ce que lâutilisateur est autorisĂ© Ă faire.
-
UtilisĂ© pour accĂ©der aux ressources : chaque requĂȘte vers une API inclut cet access token.
-
Format JWT : il est autoportant, signĂ©, et peut ĂȘtre vĂ©rifiĂ© sans requĂȘte Ă la base de donnĂ©es.
-
Durée de vie longue : plusieurs jours, voire semaines.
-
UtilisĂ© pour obtenir un nouvel access token sans que lâutilisateur se reconnecte.
-
Stocké de maniÚre sécurisée : souvent cÎté serveur ou dans un cookie httpOnly.
-
Peut ĂȘtre rĂ©voquĂ© : contrairement Ă lâaccess token, il est souvent stockĂ© en base et donc traçable.
đŻ Il permet de maintenir une session fluide sans compromettre la sĂ©curitĂ©.
Lâaccess token est comme une carte dâaccĂšs temporaire, tandis que le refresh token est le mĂ©canisme qui permet de prolonger cette carte sans redemander lâidentitĂ© Ă chaque fois. Ce duo permet de concilier sĂ©curitĂ© stricte et fluiditĂ© dâusage.
Lorsque l'on demande une ressource via REST, l'application doit vérifier l'autorisation, via la manipulation du Bearer token.
Pour mettre en place cette feature, on doit créer une classe qui hérite de OncePerRequestFilter,
il s'agit d'une méthode qui est invoqué une fois qu'une requete arrive dans le serveur.
public class JwtAuthorizationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// On récupÚre le Bearer
// On décode le token
// On set l'utilisateur
}
}Sans oublier d'ajouter cette méthode en guise de filtre dans la configuration principale de la classe SecurityConfiguration :
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationConfiguration authenticationConfiguration) throws Exception {
...
.addFilterBefore(new JwtAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}Et maintenant, je peux accéder aux ressources de l'application, ex :
GET /users (avec le Bearer token dans le champs Authorization du header de la requete).
Dans mon application, je dois ĂȘtre admin pour accĂ©der Ă certaines requetes et user pour d'autres.
Comment faire ?
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationConfiguration authenticationConfiguration) throws Exception {
http
.authorizeHttpRequests((authz) -> authz.requestMatchers(HttpMethod.GET, "/users/**").hasAuthority("USER"))
.authorizeHttpRequests((authz) -> authz.requestMatchers(HttpMethod.POST, "/users/**").hasAuthority("ADMIN"))En ajoutant des annotation aux méthodes (controller ou service) :
@PostAuthorize("hasAuthority('ADMIN')")
@PostMapping("/users")
public AppUser register(@RequestBody UserForm userForm) { }
//
@PostAuthorize("hasAuthority('USER')")
@GetMapping("/users")
public List<AppUser> userList() { }Sans oublier d'ajouter l'annotation suivante dans la class root :
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SpringSecurityApplication { }Maintenant chaque controller/service peut ĂȘtre filtrĂ© en fonction du ou des roles de l'utilisateur.
En cas d'expiration du token d'accĂšs, on a 2 solution :
- S'autentifier (mauvaise solution).
- On utilise le refresh token pour en générer un nouveau.
On le fait via le controller :
@GetMapping("/refreshToken")
public void refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException { }Ceci permet de générer un nouveau access token à chaque fois qu'il a expiré.







