Authentification de l'API REST avec Spring Security et MongoDB

Bonjour à tous! En partant pour le week-end, nous partageons avec vous un article qui a été traduit avant le début du cours "Developer on the Spring Framework" .





Dans les articles précédents, nous avons créé un service Web RESTful, maintenant nous allons parler de sécurité

introduction


Dans un article prĂ©cĂ©dent, nous avons examinĂ© comment crĂ©er une API REST Ă  l'aide des frameworks Java Spring Boot et MongoDB. L'API, cependant, ne nĂ©cessitait aucune authentification, ce qui signifie qu'elle n'est probablement pas encore prĂȘte Ă  l'emploi. Par consĂ©quent, ce guide vous montrera comment utiliser l'environnement de sĂ©curitĂ© intĂ©grĂ© de Spring pour ajouter un niveau d'authentification Ă  cette API.

Pourquoi notre API a-t-elle besoin d'une authentification?


Les API fournissent une interface simple pour interagir avec les données internes, il est donc logique que vous ne vouliez pas que quiconque ait accÚs à ces données et les modifie. L'authentification garantit que seuls les utilisateurs fiables peuvent accéder à l'API.

Comment ça fonctionne


Nous utiliserons l' authentification HTTP de base , qui utilise un nom d'utilisateur et un mot de passe. Le nom d'utilisateur et le mot de passe sont séparés sur une ligne par deux points au format suivant username:password.

Cette ligne est ensuite encodĂ©e en utilisant l'encodage Base64 , donc la ligne admin:p@55w0Rdsera encodĂ©e dans la ligne suivante YWRtaW46cEA1NXcwUmQ=(bien que je suggĂšre d'utiliser un mot de passe plus fort que «p @ 55w0Rd»). Nous pouvons attacher cette authentification Ă  nos demandes en ajoutant un en-tĂȘte Authentication. Cet en-tĂȘte pour l'exemple prĂ©cĂ©dent ressemblerait Ă  ceci (oĂč «de base» signifie que le mot de passe utilise l'authentification HTTP de base):

Authentication: Basic YWRtaW46cEA1NXcwUmQ=

Comment Spring gÚre la sécurité


Spring propose un complĂ©ment appelĂ© Spring Security , qui rend l'authentification hautement personnalisable et extrĂȘmement simple. Nous pouvons mĂȘme utiliser certaines des compĂ©tences que nous avons apprises dans un post prĂ©cĂ©dent lors de la configuration!

De quoi avons nous besoin


  • Une nouvelle collection dans notre instance MongoDB appelĂ©e "utilisateurs"
  • Un nouveau document dans la collection des utilisateurs avec les champs suivants (tous les autres champs sont facultatifs, mais ceux-ci sont nĂ©cessaires): nom d'utilisateur, mot de passe (hachĂ© en utilisant l'algorithme BCrypt, plus Ă  ce sujet plus tard)
  • Sources du post prĂ©cĂ©dent

BCrypt pour le hachage de mot de passe


Le hachage est un algorithme de chiffrement Ă  sens unique. En fait, aprĂšs le hachage, il est presque impossible de dĂ©couvrir Ă  quoi ressemblaient les donnĂ©es d'origine. L'algorithme de hachage BCrypt salit d'abord un morceau de texte, puis le hache en une chaĂźne de 60 caractĂšres. L'encodeur Java BCrypt propose une mĂ©thode matchesqui vĂ©rifie si une chaĂźne correspond Ă  un hachage. Par exemple, un mot de passe p@55w0RdhachĂ© avec BCrypt peut avoir une signification $2b$10$Qrc6rGzIGaHpbgPM5kVXdeNZ9NiyRWC69Wk/17mttHKnDR2lW49KS. Lors de l'appel de la mĂ©thode matchesBCrypt pour un mot de passe non chiffrĂ© et hachĂ©, nous obtenons une valeur true. Ces hachages peuvent ĂȘtre gĂ©nĂ©rĂ©s Ă  l'aide de l'encodeur BCrypt intĂ©grĂ© Ă  Spring Security.

Pourquoi devrions-nous hacher les mots de passe?


Nous avons tous entendu parler de cyberattaques récentes qui ont entraßné le vol de mots de passe de grandes entreprises. Alors pourquoi est-il seulement recommandé de changer nos mots de passe aprÚs le piratage? Parce que ces grandes entreprises ont fait en sorte que les mots de passe soient toujours hachés dans leurs bases de données!

Bien qu'il vaille toujours la peine de changer de mot de passe aprĂšs de tels hacks de donnĂ©es, le hachage de mot de passe rend extrĂȘmement difficile la recherche du vrai mot de passe de l'utilisateur, car il s'agit d'un algorithme Ă  sens unique. En fait, cela peut prendre des annĂ©es pour dĂ©chiffrer correctement un mot de passe complexe hachĂ©. Cela offre un niveau de protection supplĂ©mentaire contre le vol de mot de passe. Et Spring Security simplifie le hachage, donc la vraie question devrait ĂȘtre: "Pourquoi pas?"

Ajout d'un utilisateur Ă  MongoDB


J'ajouterai un minimum de champs nécessaires à ma collection users(utilisateurs), donc un document avec des utilisateurs dans ma base de données ne contiendra que username(nom d'utilisateur) et haché BCrypt password(mot de passe). Dans cet exemple, mon nom d'utilisateur sera adminet mon mot de passe le sera welcome1, mais je suggérerais d'utiliser un nom d'utilisateur et un mot de passe plus robustes dans l'API de niveau de production.

db.users.insert({
  “username” : “admin”,
  “password” : “$2a$10$AjHGc4x3Nez/p4ZpvFDWeO6FGxee/cVqj5KHHnHfuLnIOzC5ag4fm”
});

Ce sont tous les paramÚtres nécessaires dans MongoDB! Le reste de la configuration se fera dans notre code Java.

Ajout d'un modÚle utilisateur et d'un référentiel


Le post précédent décrit en détail les modÚles et les référentiels de Mongo, donc je n'entrerai pas dans les détails sur leur fonctionnement ici - si vous voulez rafraßchir vos connaissances, n'hésitez pas à visiter mon post précédent!

L'inconvĂ©nient est que Spring doit savoir Ă  quoi ressemblera le document user(modĂšle) et comment accĂ©der Ă  la collection userdans la base de donnĂ©es (rĂ©fĂ©rentiels). Nous pouvons placer ces fichiers dans les mĂȘmes dossiers de modĂšles et de rĂ©fĂ©rentiels, respectivement, comme nous l'avons fait dans l'exercice prĂ©cĂ©dent.

ModĂšle


Le modÚle sera une classe de base Java avec custom _id, usernameet password. Le fichier sera nommé Users.java. et ressemblera à ceci:

package com.example.gtommee.rest_tutorial.models;

import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;

public class Users {
  @Id
  public ObjectId _id;

  public String username;
  public String password;

  public Users() {}

  public Users(ObjectId _id, String username, String password) 
{
    this._id = _id;
    this.username = username;
    this.password = password;
  }

  public void set_id(ObjectId _id) { this._id = _id; }

  public String get_id() { return this._id.toHexString(); }

  public void setPassword(String password) { this.password = password; }

  public String getPassword() { return password; }

  public void setUsername(String username) { this.username = username; }

  public String getUsername() { return username; }
}

DĂ©pĂŽt


Le référentiel sera appelé UsersRepository.javaet ressemblera à ceci - rappelez-vous, nous devrons trouver des utilisateurs par eux username, nous devrons donc inclure la méthode findByUsernamedans l'interface du référentiel.

package com.example.gtommee.rest_tutorial.repositories;

import com.example.gtommee.rest_tutorial.models.Users;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface UsersRepository extends MongoRepository<Users, String> {
  Users findByUsername(String username);
}

Et c'est tout pour le modÚle et le référentiel!

Ajout de dépendances de sécurité


Il devrait y avoir un fichier avec un nom dans le répertoire racine du projet pom.xml. Nous n'avons pas encore touché ce fichier, mais le fichier pom contient toutes les dépendances de notre projet, et nous allons en ajouter quelques-unes, alors commençons par ouvrir ce fichier et faire défiler jusqu'à la balise . La seule nouvelle dépendance dont nous avons besoin est la sécurité du démarreur à ressort . Spring a un gestionnaire de versions intégré, donc la dépendance que nous devons ajouter à la balise est la suivante:<dependencies>

<dependencies>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Et Maven tĂ©lĂ©chargera les fichiers source pour nous, donc nos dĂ©pendances doivent ĂȘtre prĂȘtes Ă  l'emploi!

Créer un service d'authentification


Nous devons indiquer Ă  Spring oĂč se trouvent nos donnĂ©es utilisateur et oĂč trouver les informations nĂ©cessaires Ă  l'authentification. Pour ce faire, nous pouvons crĂ©er un service d'authentification (Service d'authentification). Commençons par crĂ©er un nouveau dossier dans les src/main/resources/java/[package name]services appelĂ©s, et nous pouvons crĂ©er un nouveau fichier dans ce dossier de configuration avec le nom MongoUserDetailsService.java.

MongoUserDetailsService.java


Cette classe a un composant principal, je vais donc donner toute la classe ici et l'expliquer ci-dessous:

package com.example.gtommee.rest_tutorial.services;

import com.example.gtommee.rest_tutorial.models.Users;
import com.example.gtommee.rest_tutorial.repositories.UsersRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Component
public class MongoUserDetailsService implements UserDetailsService{
  @Autowired
  private UsersRepository repository;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    Users user = repository.findByUsername(username);

    if(user == null) {
      throw new UsernameNotFoundException(“User not found”);
    }

    List<SimpleGrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority(“user”));

    return new User(user.getUsername(), user.getPassword(), authorities);
  }
}

Cet extrait commence par les importations dont nous avons besoin dans le fichier. De plus, la section implements UserDetailsServiceindique que cette classe crĂ©era un service de recherche et d'authentification d'utilisateurs. Ensuite, l'annotation @Componentindique que cette classe peut ĂȘtre incorporĂ©e dans un autre fichier (par exemple, le fichier SecurityConfiguration, que nous allons parcourir en plusieurs sections).

Annotation @Autowiredterminée private UsersRepository repository; est un exemple d'implémentation, cette propriété nous fournit une instance de la nÎtre UsersRepositorypour le travail. L'annotation @Overrideindique que cette méthode sera utilisée à la place de la méthode UserDetailsService par défaut. Tout d'abord, cette méthode obtient l'objet à Userspartir de la source de données MongoDB en utilisant la méthode findByUsernameque nous avons déclarée dans UsersRepository.

La méthode vérifie ensuite si l'utilisateur a été trouvé ou non. Ensuite, l'utilisateur se voit accorder des autorisations / rÎles (cela peut ajouter des niveaux d'authentification supplémentaires pour les niveaux d'accÚs, mais un rÎle sera suffisant pour cette leçon). Enfin, la méthode renvoie un objet printemps Useravec username, passwordet l' roleutilisateur authentifié.

Créer une configuration de sécurité


Nous devrons redéfinir certains des protocoles de sécurité intégrés de Spring pour utiliser notre base de données et notre algorithme de hachage, nous avons donc besoin d'un fichier de configuration spécial. Pour le créer, nous devons créer un nouveau dossier src/main/resources/java/[package name]avec le nom config, et nous devons également créer un nouveau fichier dans ce dossier de configuration avec le nom SecurityConfiguration.java. Ce fichier comporte plusieurs parties importantes, commençons donc par la classe de base SecurityConfiguration:

SecurityConfiguration.java


package com.example.gtommee.rest_tutorial.config;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableConfigurationProperties
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 @Autowired
 MongoUserDetailsService userDetailsService;
}

Il y a déjà assez de choses à traiter, alors commençons par le haut. Une annotation @Configurationindique que la classe contiendra des beans Java, décrits en détail ici. Une annotation @EnableConfigurationPropertiesindique ce que la classe contiendra en tant que bean de configuration spécial. Ensuite, l'instruction extends WebSecurityConfigurerAdaptermappera la classe parente WebSecurityConfigurerAdapterà notre classe de configuration , fournissant à notre classe tout le nécessaire pour garantir le respect de ses rÚgles de sécurité. Enfin, la classe injectera automatiquement une instance ( @Autowired) MongoUserDetailsService, que nous pourrons utiliser plus loin dans ce fichier.

Étape d'authentification


Ensuite, nous devons indiquer à Spring Security comment nous voulons gérer l'authentification des utilisateurs. Par défaut, Spring Security a un nom d'utilisateur et un mot de passe prédéfinis, une protection CSRF et une gestion de session . Cependant, nous voulons que nos utilisateurs utilisent leur nom d'utilisateur et leur mot de passe pour accéder à la base de données. De plus, puisque nos utilisateurs seront réauthentifiés à chaque demande, plutÎt que de se connecter, nous n'avons pas besoin de protection CSRF et de gestion de session, nous pouvons donc ajouter une méthode avec un nom configurequi remplace le schéma d'authentification par défaut pour dire à Spring exactement comment nous voulons gérer l'authentification, et ressemblera à ceci:

@Override
protected void configure(HttpSecurity http) throws Exception {
 http
   .csrf().disable()
   .authorizeRequests().anyRequest().authenticated()
   .and().httpBasic()
   .and().sessionManagement().disable();
}

Encore une fois, il se passe beaucoup de choses ici, donc nous allons rĂ©gler cela par Ă©tapes. L'annotation @Overrideindique Ă  Spring Boot d'utiliser la mĂ©thode configure (HttpSecurity http)au lieu de la configuration Spring par dĂ©faut. Ensuite, nous appelons une sĂ©rie de mĂ©thodes pour l'objet httpoĂč la configuration rĂ©elle a lieu. Ces mĂ©thodes procĂšdent comme suit:

  • csrf().disable(): DĂ©sactive la protection CSRF car elle n'est pas nĂ©cessaire pour l'API
  • authorizeRequests().anyRequest().authenticated(): DĂ©clare que toutes les demandes adressĂ©es Ă  un noeud final doivent ĂȘtre autorisĂ©es, sinon elles doivent ĂȘtre rejetĂ©es.
  • and().httpBasic(): Springde sorte qu'il attend l'authentification HTTP de base (discutĂ©e ci-dessus).
  • .and().sessionManagement().disable(): indique Ă  Spring de ne pas stocker les informations de session pour les utilisateurs, car cela n'est pas nĂ©cessaire pour l'API

Ajout d'un encodeur Bcrypt


Maintenant, nous devons dire Ă  Spring d'utiliser l'encodeur BCrypt pour hacher et comparer les mots de passe - cela semble ĂȘtre une tĂąche difficile, mais en fait, c'est trĂšs simple. Nous pouvons ajouter cet encodeur en ajoutant simplement les lignes suivantes Ă  notre classe SecurityConfiguration:

@Bean
public PasswordEncoder passwordEncoder() {
   return new BCryptPasswordEncoder();
}

Et tout le travail! Ce bean simple indique à Spring que le PasswordEncoder que nous voulons utiliser est Spring Boot BCryptPasswordEncoder()pour encoder et comparer les hachages de mot de passe. Spring Boot comprend également plusieurs autres encodeurs de mot de passe - je vous recommande de les essayer si vous voulez expérimenter!

Spécifiez le gestionnaire d'authentification


Enfin, nous devons indiquer dans notre SecurityConfigurationque nous voulons utiliser MongoUserDetailsService(que nous avons créé dans la section précédente) pour notre authentification. Nous pouvons le faire en utilisant la méthode suivante:

@Override
public void configure(AuthenticationManagerBuilder builder) 
throws Exception {
  builder.userDetailsService(userDetailsService);
}

Cette méthode remplace simplement la configuration par défaut AuthenticationManagerBuilder, remplaçant plutÎt notre propre service de transfert de données personnalisé.

Fichier final SecurityConfiguration.java


import com.example.gtommee.rest_tutorial.services.MongoUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableConfigurationProperties
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 @Autowired
 MongoUserDetailsService userDetailsService;

 @Override
 protected void configure(HttpSecurity http) throws Exception {
   http
     .csrf().disable()
     .authorizeRequests().anyRequest().authenticated()
     .and().httpBasic()
     .and().sessionManagement().disable();
 }

 @Bean
 public PasswordEncoder passwordEncoder() {
   return new BCryptPasswordEncoder();
 }

 @Override
 public void configure(AuthenticationManagerBuilder builder) 
throws Exception {
   builder.userDetailsService(userDetailsService);
 }
}

ContrĂŽle d'authentification


Je vais tester une GETdemande rapide avec une authentification valide et incorrecte pour m'assurer que la configuration fonctionne comme prévu. URL

/ nom d'utilisateur / mot de passe incorrect

: http://localhost:8080/pets/
MĂ©thode: GET
Connexion: Basic YWRtaW46d2VsY29tZQ==

RĂ©ponse:

401 Unauthorized



URL de nom d' utilisateur / mot de passe valide : http://localhost:8080/pets/
MĂ©thode: GET
Connexion: Basic YWRtaW46d2VsY29tZTE=

RĂ©ponse:

[
 {
   “_id”: “5aeccb0a18365ba07414356c”,
   “name”: “Spot”,
   “species”: “dog”,
   “breed”: “pitbull”
 },
 {
   “_id”: “5aeccb0a18365ba07414356d”,
   “name”: “Daisy”,
   “species”: “cat”,
   “breed”: “calico”
 },
 {
   “_id”: “5aeccb0a18365ba07414356e”,
   “name”: “Bella”,
   “species”: “dog”,
   “breed”: “australian shepard”
 }
]

Conclusion


Cela fonctionne comme il se doit! L'authentification HTTP de base pour l'API Spring Boot peut ĂȘtre dĂ©licate, mais nous espĂ©rons que ce guide contribuera Ă  la rendre plus comprĂ©hensible. L'authentification est un incontournable dans le cyber-climat d'aujourd'hui, donc des outils comme Spring Security sont essentiels pour garantir l'intĂ©gritĂ© et la sĂ©curitĂ© de vos donnĂ©es.

C'est tout! Retrouvez-moi sur le parcours .

All Articles