Comment configurer l'autorisation et l'authentification JWT Java Spring Boot

Le mois dernier, j'ai eu la chance d'implémenter l'authentification JWT pour un projet parallèle. J'ai déjà travaillé avec JWT dans Ruby on Rails, mais c'était ma première fois au printemps.

Dans cet article, j'essaierai d'expliquer ce que j'ai appris et appliqué dans mon projet pour partager mon expérience et j'espère aider certaines personnes.

Nous commencerons par jeter un rapide coup d'œil à la théorie derrière JWT et à son fonctionnement. Ensuite, nous verrons comment l'implémenter dans une application Spring Boot.

Bases de JWT

JWT, ou JSON Web Tokens (RFC 7519), est une norme principalement utilisée pour sécuriser les API REST. Bien qu'il s'agisse d'une technologie relativement nouvelle, elle gagne rapidement en popularité.

Dans le processus d'authentification JWT, le frontal (client) envoie d'abord quelques informations d'identification pour s'authentifier (nom d'utilisateur et mot de passe dans notre cas, puisque nous travaillons sur une application Web).

Le serveur (l'application Spring dans notre cas) vérifie ensuite ces informations d'identification, et si elles sont valides, il génère un JWT et le renvoie.

Après cette étape, le client doit fournir ce jeton dans l'en- tête Authorization de la demande dans le formulaire «Bearer TOKEN». Le back-end vérifiera la validité de ce token et autorisera ou rejettera les demandes. Le jeton peut également stocker des rôles d'utilisateur et autoriser les demandes en fonction des autorités données.

la mise en oeuvre

Voyons maintenant comment nous pouvons implémenter le mécanisme de connexion et d'enregistrement JWT dans une vraie application Spring.

Dépendances

Vous pouvez voir la liste des dépendances Maven que notre exemple de code utilise ci-dessous. Notez que les dépendances principales telles que Spring Boot et Hibernate ne sont pas incluses dans cette capture d'écran.

Enregistrement des utilisateurs

Nous commencerons par créer des contrôleurs pour enregistrer les utilisateurs en toute sécurité et les authentifier en fonction du nom d'utilisateur et du mot de passe.

Nous avons une entité modèle appelée User. Il s'agit d'une classe d'entité simple qui correspond à la table USER . Vous pouvez utiliser toutes les propriétés dont vous avez besoin en fonction de votre application.

Nous avons également une classe UserRepository simple pour enregistrer les utilisateurs. Nous devons remplacer la méthode findByUsername car nous l'utiliserons dans l'authentification.

public interface UserRepository extends JpaRepository{ User findByUsername(String username); }

Nous ne devons jamais stocker de mots de passe en texte brut dans la base de données car de nombreux utilisateurs ont tendance à utiliser le même mot de passe pour plusieurs sites.

Il existe de nombreux algorithmes de hachage différents, mais le plus couramment utilisé est BCrypt et c'est une méthode recommandée de hachage sécurisé. Vous pouvez consulter cet article pour plus d'informations sur le sujet.

@Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); }

Pour hash du mot de passe, nous allons définir un Bcrypt haricot dans @SpringBootApplication et annoter la classe principale comme suit:

Nous appellerons les méthodes sur ce bean lorsque nous aurons besoin de hacher un mot de passe.

Nous avons également besoin d'un UserController pour enregistrer les utilisateurs. Nous créons le contrôleur, l' annotons avec @RestController et définissons le mappage correspondant.

Dans notre application, nous enregistrons l'utilisateur en fonction d'un objet DTO qui est passé depuis le frontal. Vous pouvez également transmettre un objet User dans @RequestBody .

Après avoir passé l'objet DTO, nous chiffrons le champ de mot de passe à l'aide du bean BCrypt que nous avons créé précédemment. Vous pouvez également le faire dans le contrôleur, mais il est préférable de placer cette logique dans la classe de service.

@Transactional(rollbackFor = Exception.class) public String saveDto(UserDto userDto) { userDto.setPassword(bCryptPasswordEncoder.encode(userDto.getPassword())); return save(new User(userDto)).getId(); }

Filtre d'authentification

Nous avons besoin d'une authentification pour nous assurer que l'utilisateur est vraiment celui qu'il prétend être. Nous utiliserons la paire classique nom d'utilisateur / mot de passe pour ce faire.

Voici les étapes pour implémenter l'authentification:

  1. Créez notre filtre d'authentification qui étend UsernamePasswordAuthenticationFilter
  2. Créez une classe de configuration de sécurité qui étend WebSecurityConfigurerAdapter et appliquez le filtre

Voici le code de notre filtre d'authentification - comme vous le savez peut-être, les filtres sont l'épine dorsale de Spring Security.

Passons en revue ce code étape par étape.

Cette classe étend UsernamePasswordAuthenticationFilter qui est la classe par défaut pour l'authentification par mot de passe dans Spring Security. Nous l'étendons pour définir notre logique d'authentification personnalisée.

Nous appelons la méthode setFilterProcessesUrl dans notre constructeur. Cette méthode définit l'URL de connexion par défaut sur le paramètre fourni.

Si vous supprimez cette ligne, Spring Security crée le point de terminaison «/ login» par défaut. Il définit le point de terminaison de connexion pour nous, c'est pourquoi nous ne définirons pas explicitement un point de terminaison de connexion dans notre contrôleur.

Après cette ligne, notre point de terminaison de connexion sera / api / services / controller / user / login . Vous pouvez utiliser cette fonction pour rester cohérent avec vos points de terminaison.

Nous remplaçons les méthodes tryAuthentication et successfulAuthentication de la classe UsernameAuthenticationFilter .

La fonction tryAuthentication s'exécute lorsque l'utilisateur tente de se connecter à notre application. Il lit les informations d'identification, crée un POJO utilisateur à partir d'elles, puis vérifie les informations d'identification pour s'authentifier.

Nous transmettons le nom d'utilisateur, le mot de passe et une liste vide. La liste vide représente les autorités (rôles) et nous la laissons telle quelle car nous n'avons pas encore de rôle dans notre application.

Si l'authentification est réussie, la successfulAuthentication méthode fonctionne. Les paramètres de cette méthode sont transmis par Spring Security dans les coulisses.

The attemptAuthentication method returns an Authentication object that contains the authorities we passed while attempting.

We want to return a token to user after authentication is successful, so we create the token using username, secret, and expiration date. We need to define the SECRET and EXPIRATION_DATE now.

We create a class to be a container for our constants. You can set the secret to whatever you want, but the best practice is making the secret key as long as your hash. We use the HS256 algorithm in this example, so our secret key is 256 bits/32 chars.

The expiration time is set to 15 minutes, because it is the best practice against secret key brute-forcing attacks. The time is in milliseconds.

We have prepared our Authentication filter, but it is not active yet. We also need an Authorization filter, and then we will apply them both through a configuration class.

This filter will check the existence and validity of the access token on the Authorization header. We will specify which endpoints will be subject to this filter in our configuration class.

Authorization Filter

The doFilterInternal method intercepts the requests then checks the Authorization header. If the header is not present or doesn’t start with “BEARER”, it proceeds to the filter chain.

If the header is present, the getAuthentication method is invoked. getAuthentication verifies the JWT, and if the token is valid, it returns an access token which Spring will use internally.

This new token is then saved to SecurityContext. You can also pass in Authorities to this token if you need for role-based authorization.

Our filters are ready, and now we need to put them into action with the help of a configuration class.

Configuration

We annotate this class with @EnableWebSecurity and extend WebSecurityConfigureAdapter to implement our custom security logic.

We autowire the BCrypt bean that we defined earlier. We also autowire the UserDetailsService to find the user’s account.

The most important method is the one which accepts an HttpSecurity object. Here we specify the secure endpoints and filters that we want to apply. We configure CORS, and then we permit all post requests to our sign up URL that we defined in the constants class.

You can add other ant matchers to filter based on URL patterns and roles, and you can check this StackOverflow question for examples regarding that. The other method configures the AuthenticationManager to use our encoder object as its password encoder while checking the credentials.

Testing

Let’s send a few requests to test if it works properly.

Here we send a GET request to access a protected resource. Our server responds with a 403 code. This is the expected behavior because we haven’t provided a token in the header. Now let’s create a user:

To create a user, we send a post request with our User DTO data. We will use this user to login and get an access token.

Great! We got the token. After this point, we will use this token to access protected resources.

We provide the token in the Authorization header and we are now allowed access to our protected endpoint.

Conclusion

In this tutorial I have walked you through the steps I took when implementing JWT authorization and password authentication in Spring. We also learned how to save a user securely.

Thank you for reading – I hope it was helpful to you. If you are interested in reading more content like this, feel free to subscribe to my blog at //erinc.io. :)