Ajouter jwt à spring boot
Dans cet article, nous allons créer une application Spring Boot basée sur l’API REST pour démontrer l’utilisation de Spring Boot 3, Spring Security 6 et de la dernière version de JWT. L’application dispose d’un point de terminaison de connexion qui accepte le nom d’utilisateur/mot de passe pour la connexion et génère un jeton basé sur JWT après une authentification réussie. Le processus de connexion sera basé sur les rôles et configurable dans la base de données. Nous utiliserons la base de données MYSQL et Spring JPA pour cette application de démonstration afin de lire toutes les informations relatives à l’utilisateur et seul l’utilisateur autorisé pourra accéder aux API REST sécurisées.
Structure
du projet La structure initiale du projet est générée à partir de https://start.spring.io en sélectionnant la version 3.2.4 de Spring Boot basée sur Maven comme 3.2.4 et Java 17.
La structure du projet a une structure de projet Spring Boot classique où nous avons toute la configuration liée à la sécurité dans le package de configuration et les packages correspondants pour l’implémentation de controller, service, model classes. La configuration MYSQL réside dans le fichier application.proprties.
Vous trouverez ci-dessous les autres dépendances Maven qui ont été ajoutées manuellement pour MySQL connector 8, JWT 0.12 et Lombok 1.18
: <dependency> <groupId>mysql <artifactId>mysql-connector-java <version>8.0.33 <dependency> <groupId>io.jsonwebtoken <artifactId> >jjwt <version>0.12.5 <dependency> <groupId>org.projectlombok <artifactId>lombok <version>1.18.32 <scope> provided Implémentation de laclasse de modèle
Principalement, il existe 2 classes de modèle - User.java et Role.java. Les classes d’entités User et Role ont plusieurs à plusieurs relation.
User.java @Data@Entity@Table(name = « users ») public class User { @Id@GeneratedValue(strategy= GenerationType.AUTO) private long id ; @Columnprivate String username ; @Columnprivate String name ; @Columnprivate String email ; @Column@JsonIgnoreprivate String password ; @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinTable(name = « user_role », joinColumns = { @JoinColumn(name = « user_id ») }, inverseJoinColumns = { @JoinColumn(name = « role_id ») }) private Set<Role> roles ; } Role.java @Data@Entity@Table(name = « roles ») public class Role { @Id@GeneratedValue(strategy= GenerationType.AUTO) private long id ; @Enumerated(EnumType.STRING) @Column(name ="role_name », unique = true) private RoleType role ; @Column(name = « description ») private String description ; }Nous avons défini le rôle de l’utilisateur pour qu’il soit de type Enum.
public enum RoleType { UTILISATEUR(« ROLE_USER »), ADMIN(« ROLE_ADMIN ») ; private Valeur String ; RoleType(String value) { this.value = value ; } public String getValue() { return this.value ; } }Ensuite, nous avons défini une LoginDto.java pour contenir le nom d’utilisateur/mot de passe lors de l’appel d’API de connexion.
@Datapublic class LoginDto { private String username ; private String password ;; }Implémentation de Spring Controller
Maintenant, voyons les API que nous exposons pour créer cet exemple d’application d’exemple. Il y a 2 API développées ici - l’une est pour la connexion qui ne nécessite aucune authentification pour l’accès tandis que nous avons une autre API dans laquelle est une API sécurisée.
AuthController.java @RestController@RequestMapping(« /auth ») public class AuthController { @Autowiredprivate UserService userService ; @PostMapping(« /login ») public ResponseEntity<String> login(@RequestBody LoginDto loginDto) { return ResponseEntity.ok(userService.login(loginDto)) ; } } UserController.java @RestController@RequestMapping(« /user ») public class UserController { @Autowiredprivate UserService userService ; @GetMappingpublic ResponseEntity<User> userProfile() { return ResponseEntity.ok(userService.getUser()) ; } }Implémentation de la classe Service et JPA
Repository Créons une classe de service et de référentiel simple pour exécuter l’application Spring Boot. Ensuite, nous pouvons ajouter de la sécurité aux API exposées.
Les implémentations de service et de classe de référentiel sont des implémentations de base et auto-explicatives. Faites-moi savoir dans la section des commentaires si vous avez des questions à ce sujet.
UserRepository.java’interface publique UserRepository étend JpaRepository<User, Long> { User findByUsername(String username) ; }Nous allons aborder toutes les dépendances injectées telles que bcryptEncoder, jwtTokenService et authenticationProvider plus loin dans l’article.
La méthode utilise le UsernamePasswordAuthenticationToken de Spring Securty pour authentifier l’utilisateur à partir de la base de données en fonction de l’AuthenticationProvider que nous avons configuré dans le tandis que la méthode est appelée pour récupérer l’utilisateur à partir de l’authentification après la base de données.
un jeton JWT sera généré lors d’une authentification réussie.
UserServiceImpl.java @Service(value = « userService ») public class UserServiceImpl implémente UserService { @Autowiredprivate BCryptPasswordEncoder bcryptEncoder ; @Autowiredprivate UserRepository userRepository ; @Autowiredprivate JwtTokenService jwtTokenService ; @Autowiredprivate AuthenticationProvider authenticationProvider ; @Overridepublic String login(LoginDto loginDto) { final Authentication authentication = authenticationProvider.authenticate( new UsernamePasswordAuthenticationToken( loginDto.getUsername(), loginDto.getPassword() ) ) ; SecurityContextHolder.getContext().setAuthentication(authentification) ; final User user = userRepository.findByUsername(loginDto.getUsername()) ; return jwtTokenService.generateToken(utilisateur.getNomUtilisateur(), utilisateur.getRoles()) ; } @Overridepublic Utilisateur getUser() { UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal() ; return userRepository.findByUsername(userDetails.getUsername()) ; }Vous trouverez ci-dessous l’entrée dans le fichier pour la configuration de la source de données.
spring.application.name=springbootdemo server.port=8080 server.servlet.context-path=/spring-boot-demo spring.datasource.url=jdbc :mysql ://localhost :3306/test spring.datasource.username=root spring.datasource.password=rootSpring Security 6
Configuration Selon Spring Security 6, nous devons définir un Bean de type qui est responsable pour toute la configuration liée à la sécurité de n’importe quelle application Spring Boot. Ici, dans cette méthode, nous demandons à Spring Security de désactiver et de sécuriser toutes les API sauf celles qui sont blanches et nous avons inséré notre filtre personnalisé avant que Spring ne le fournisse pour valider le jeton et définir le contexte de sécurité.
De plus, nous avons configuré le sens que notre mot de passe Bcrypt est crypté et enregistré dans la base de données et nous demandons à Spring d’utiliser le mécanisme de cryptage tout en faisant correspondre le mot de passe.
Voici un outil en ligne gratuit pour générer le mot de passe Bcrypt.
WebSecurityConfig.java @Configuration@EnableWebSecurity@EnableMethodSecuritypublic classe WebSecurityConfig { private static final String[] WHITELIST_URLS = {"/auth/**"} ; @Autowiredprivate UserDetailsService userDetailsService ; @Autowiredprivate JwtAuthenticationFilter jwtAuthFilter ; @Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(AbstractHttpConfigurer ::d isable) .csrf(AbstractHttpConfigurer ::d isable) .authorizeHttpRequests(auth -> { auth.requestMatchers(WHITELIST_URLS).permitAll().anyRequest().authenticated() ; }) .sessionManagement(session -> session.creationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) ; return http.build() ; } @Bean AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider provider = nouveau DaoAuthenticationProvider() ; provider.setUserDetailsService(userDetailsService) ; provider.setPasswordEncoder(passwordEncoder()) ; fournisseur de retour ; } @Beanpublic BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder() ; } }Maintenant, définissons notre UserDetailsServiceImpl qui est injecté dans . Cette méthode est utilisée par Spring Security pour effectuer une recherche dans la base de données afin de trouver l’utilisateur en fonction de son nom d’utilisateur.
UserDetailsServiceImpl.java @Componentpublic classe UserDetailsServiceImpl implémente UserDetailsService { @Autowiredprivate UserRepository userRepository ; public UserDetails loadUserByUsername(String username) lève UsernameNotFoundException { User user = userRepository.findByUsername(username) ; if(user == null){ throw new UsernameNotFoundException(« Nom d’utilisateur ou mot de passe invalide. ») ; } return new org.springframework.security.core.userdetails.User(user.getUsername(), user. getPassword(), getAuthority(user.getRoles())) ; } private List<SimpleGrantedAuthority> getAuthority(Set<Role> roles) { return roles.stream().map(role -> new SimpleGrantedAuthority(role.getRole().getValue())).collect(Collectors.toUnmodifiableList()) ; } }Jusqu’à présent, nous avons presque terminé la mise en œuvre. Fournissons l’implémentation pour laquelle est appelée une fois pour chaque demande. Ici, nous avons l’implémentation pour déchiffrer le jeton JWT dans l’en-tête de la demande et définissez le contexte Spring Security. L’en-tête d’autorisation ressemble à ceci.
@Configurationpublic classe JwtAuthenticationFilter extends OncePerRequestFilter { @Autowiredprivate UserDetailsService userDetailsService ; @Autowiredprivate JwtTokenService jwtTokenService ; private static final String TOKEN_PREFIX = « Bearer » ; private static final String HEADER_STRING = « Autorisation » ; @Overrideprotected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) lève IOException, ServletException { String header = req.getHeader(HEADER_STRING) ; Chaîne nom d’utilisateur = null ; if (header != null && header.startsWith(TOKEN_PREFIX)) { String authToken = header.replace(TOKEN_PREFIX," ») ; username = jwtTokenService.extractUsernameFromToken(authToken) ; } else { logger.warn(« n’a pas pu trouver la chaîne de porteur, ignorera l’en-tête ») ; } if (StringUtils.hasText(username)) { UserDetails userDetails = userDetailsService.loadUserByUsername(nom d’utilisateur) ; UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()) ; authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req)) ; logger.info(« utilisateur authentifié " + nom d’utilisateur + « , définition du contexte de sécurité ») ; SecurityContextHolder.getContext().setAuthentication(authentification) ; } chain.doFilter(req, res) ; } }Intégration de JWT à Spring Security 6
Afin d’intégrer JWT à Spring Security, nous avons besoin d’un mécanisme pour générer un jeton JWT dont le nom d’utilisateur, les rôles et le délai d’expiration seront cryptés avec une phrase de passe forte. Vous trouverez ci-dessous une classe d’utilitaire qui a toutes les méthodes définies pour effectuer ces opérations.
JwtTokenService.java @Componentpublic classe JwtTokenService { private String secretKey = « NllmZHptNVVrNG9RRUs3NllmZHptNVVrNG9RRUs3NllmZHptNVVrNG9RRUs3NllmZHptNVVrNG9RRUs3NllmZHptNVVrNG9RRUs3NllmZHptNVVrNG9RRUs3NllmZHptNVVrNG9RRUs3Nl » ; privé statique final long ACCESS_TOKEN_VALIDITY_SECONDS = 5*60*60 ; public String generateToken(String username, Set<Role> authorities) { return Jwts.builder().subject(username) .claim(« roles », authorities)//can also set a map .issuedAt(new Date(System.currentTimeMillis())) .expiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS * 1000)) .issuer(« https://www.devglan.com ») .signWith(getSecretKey(), SignatureAlgorithm.HS512) .compact() ; } public String extractUsernameFromToken(String token) { if (isTokenExpired(token)) { return null ; } return getClaims(token, Claims ::getSubject) ; } public <T> T getClaims(String token, Function<Claims, T> resolver) { return resolver.apply(Jwts.parser().verifyWith(getSecretKey()).build().parseSignedClaims(token).getPayload()) ; } public boolean isTokenExpired(String token) { Date expiration = getClaims(token, Claims ::getExpiration) ; return expiration.before(new Date()) ; } private SecretKey getSecretKey() { byte[] keyBytes = Decoders.BASE64.decode(secretKey) ; return Keys.hmacShaKeyFor(keyBytes) ; } }Ici, nous utilisons le HS512 comme algorithme de signature pour générer le jeton JWT.
Test de l’application
Nous pouvons exécuter le en tant qu’application Java et nous avons notre tomcat en mémoire commence à fonctionner sur le port 8080 selon la configuration dans .
Les scripts SQL par défaut créent des rôles de table (id bigint not null, description varchar(255), role_name enum ('USER','ADMIN'), clé primaire (id)) engine=InnoDB ; create table roles_seq (next_val bigint) engine=InnoDB ; insérer dans roles_seq valeurs ( 1 ) ; create table user_role (user_id bigint not null, role_id bigint not null, primary key (user_id, role_id)) engine=InnoDB ; Créer des utilisateurs de table (id bigint not null, email varchar(255), name varchar(255), password varchar(255), username varchar(255), primary key (id)) engine=InnoDB ; create table users_seq (next_val bigint) engine=InnoDB ; insérer dans users_seq valeurs ( 1 ) ; modifier les rôles de table supprimer l’index UK_716hgxp60ym1lifrdgp67xt5k ; modifier les rôles de la table, ajouter une contrainte UK_716hgxp60ym1lifrdgp67xt5k unique (role_name) ; modifier la table user_role ajouter la contrainte FKt7e7djp752sqn6w22i6ocqy6q clé étrangère (role_id) références rôles (id) ; 123456 INSERT INTO utilisateurs (id, email, nom, mot de passe, nom d’utilisateur) valeurs (1,'[email protected]', 'John Doe', '$2a$12$Ro2fUZMlItSSn0YFI2d6fujc3HbFYp2adNc47ZlQKOM7os1rTozJW', 'johndoe123') ; INSERT INTO roles(id, description, role_name) valeurs(1, 'Rôle d’utilisateur', 'UTILISATEUR') ; INSÉRER DANS user_role (user_id, role_id) valeurs (1, 1) ; @SpringBootApplicationpublic class SpringBootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication.class, args) ; Voustrouverez ci-dessous les détails de l’API exécutée sur Postman.
ConclusionDans ce tutoriel, nous avons développé des API REST à l’aide de Spring Boot 3 et de Spring Security et sécurisé l’API à l’aide du jeton de bibliothèque JWT.