Formation Java / Spring Boot – Sécurisation professionnelle d’API
À l’issue de cette journée, vous sevez capable de :
Lien vers un cours complet sur JWT & Spring Boot Security
Sans sécurité dans le cadre d’une application :
Une API bancaire doit être sécurisée. En fait, on doit sécuriser tous types d’application…
Authentification (Authentication) : Qui êtes-vous ?
Authentification
Autorisation (Autorization) : Que pouvez-vous faire ? ou Qu’avez-vous le droit de faire ?
Autorisation
Pour utiliser la sécurité de Spring, il nous faut créer une classe spécifique dédiée à la sécurité et importer une dépendance dans notre pom.xml.
pom.xml
Il suffit de le préciser lors de la création du projet Spring Boot.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
Première étape : sécurité basique.
La classe que l’on peut nommer SecurityConfig a 2 annotations @Configuration et @EnableMethodSecurity. Ensuite, on met en place un filterChain dont le rôle sera d’intercepter les requêtes pour gérer les autorisations selon les urls et les utilisateurs connectés ou pas.
SecurityConfig
@Configuration
@EnableMethodSecurity
@Configuration @EnableMethodSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/swagger-ui/**").permitAll() .requestMatchers("/v3/api-docs/**").permitAll() .requestMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() ) .httpBasic(Customizer.withDefaults()); return http.build(); } }
On utilise le service UserDetailService pour gérer un UserDetails avec une classe User. Vous voyez que c’est finalement intéressant de connaître les Design pattern comme Builder !
UserDetailService
UserDetails
User
@Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password(passwordEncoder().encode("password")) .roles("USER") .build(); UserDetails admin = User.withUsername("admin") .password(passwordEncoder().encode("admin")) .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); }
Nous verrons à quoi correspond de PasswordEncoder sachant qu’un mot de passe ne doit pas être enregistré en CLAIR !
PasswordEncoder
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
Ici, on ne va autoriser qu’un utilisateur ayant le rôle ADMIN voir les Uses Cases pour déterminer qui peut accéder ou pas aux différentes fonctionnalités. Là, on voit que la méthode supprimerCompte en peut être réalisée que par un administrateur. Le @PreAuthorize suffit et l’on peut définir plusieurs rôles.
ADMIN
@PreAuthorize
@PreAuthorize("hasRole('ADMIN')") @DeleteMapping("/{id}") public void supprimerCompte(@PathVariable Long id) { service.supprimerCompte(id); }
Allez dans Bruno et modifier le Header pour faciliter les accès :
Authorization: Basic base64(user:password)
Problème du Basic Auth :
Basic Auth
Solution :
Un JWT contient :
Exemple : xxxxx.yyyyy.zzzzz
Voir les cours complets sur JWT :
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency>
@Service public class JwtService { private final String SECRET = "cle-secrete-super-longue"; public String generateToken(String username) { return Jwts.builder() .setSubject(username) .setIssuedAt(new Date()) .setExpiration( new Date(System.currentTimeMillis() + 1000 * 60 * 60)) .signWith( Keys.hmacShaKeyFor(SECRET.getBytes()), SignatureAlgorithm.HS256) .compact(); } }
Nous allons détailer les différentes classes (vous connaissez déjà les DTO). Ici, on utilise un gestionnaire d'authentification le authenticationManager qui possède une méhtode pour instancier un objet UsernamePasswordAuthenticationToken` dans lequel on passe le username et le password.
un gestionnaire d'authentification le
qui possède une méhtode pour instancier un objet
@PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginDTO dto) { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( dto.username(), dto.password())); String token = jwtService.generateToken(dto.username()); return ResponseEntity.ok(Map.of("token", token)); }
Un filtre :
Concept simplifié :
String authHeader = request.getHeader("Authorization");
Puis validation.
Authorization: Bearer <token>
Il devrait être :
Vous savez désormais :