REST-API-Authentifizierung mit Spring Security und MongoDB

Hallo alle zusammen! Am Wochenende teilen wir Ihnen einen Artikel mit, der vor Beginn des Kurses "Developer on the Spring Framework" übersetzt wurde .





In früheren Artikeln haben wir einen RESTful-Webdienst erstellt. Jetzt werden wir uns mit Sicherheit befassen

Einführung


In einem früheren Beitrag haben wir uns angesehen, wie eine REST-API mit den Frameworks Java Spring Boot und MongoDB erstellt wird. Die API erforderte jedoch keine Authentifizierung, was bedeutet, dass sie wahrscheinlich noch nicht einsatzbereit ist. In diesem Handbuch erfahren Sie daher, wie Sie mithilfe der in Spring integrierten Sicherheitsumgebung dieser API eine Authentifizierungsstufe hinzufügen.

Warum benötigt unsere API eine Authentifizierung?


Die APIs bieten eine einfache Schnittstelle für die Interaktion mit internen Daten. Daher ist es sinnvoll, dass niemand auf diese Daten zugreifen und sie ändern soll. Durch die Authentifizierung wird sichergestellt, dass nur vertrauenswürdige Benutzer auf die API zugreifen können.

Wie es funktioniert


Wir werden die grundlegende HTTP- Authentifizierung verwenden, bei der ein Benutzername und ein Kennwort verwendet werden. Der Benutzername und das Passwort werden in einer Zeile durch einen Doppelpunkt im folgenden Format getrennt username:password.

Diese Zeile wird dann mit Base64- Codierung codiert , sodass die Zeile admin:p@55w0Rdin der nächsten Zeile codiert wird YWRtaW46cEA1NXcwUmQ=(obwohl ich vorschlagen würde, ein stärkeres Kennwort als "p @ 55w0Rd" zu verwenden). Wir können diese Authentifizierung an unsere Anforderungen anhängen, indem wir einen Header hinzufügen Authentication. Diese Überschrift für das vorherige Beispiel würde folgendermaßen aussehen (wobei "Basic" bedeutet, dass das Kennwort die grundlegende HTTP-Authentifizierung verwendet):

Authentication: Basic YWRtaW46cEA1NXcwUmQ=

Wie Spring die Sicherheit verwaltet


Spring bietet ein Add-In namens Spring Security , mit dem die Authentifizierung in hohem Maße anpassbar und äußerst einfach ist. Wir können sogar einige der Fähigkeiten verwenden, die wir in einem früheren Beitrag beim Einrichten gelernt haben!

Was brauchen wir


  • Eine neue Sammlung in unserer MongoDB-Instanz namens "Benutzer"
  • Ein neues Dokument in der Benutzersammlung mit den folgenden Feldern (alle anderen Felder sind optional, diese sind jedoch erforderlich): Benutzername, Kennwort (Hash mit dem BCrypt-Algorithmus, dazu später mehr)
  • Quellen aus dem vorherigen Beitrag

BCrypt für Passwort-Hashing


Hashing ist ein Einweg-Verschlüsselungsalgorithmus. Tatsächlich ist es nach dem Hashing fast unmöglich herauszufinden, wie die Originaldaten aussahen. Der BCrypt-Hash-Algorithmus salzt zuerst einen Text und hasht ihn dann auf eine Zeichenfolge von 60 Zeichen. Der Java BCrypt-Encoder bietet eine Methode matches, mit der überprüft wird, ob eine Zeichenfolge mit einem Hash übereinstimmt. Beispielsweise kann ein p@55w0Rdmit BCrypt gehashtes Kennwort eine Bedeutung haben $2b$10$Qrc6rGzIGaHpbgPM5kVXdeNZ9NiyRWC69Wk/17mttHKnDR2lW49KS. Beim Aufrufen der matchesBCrypt- Methode für ein unverschlüsseltes und gehashtes Kennwort erhalten wir einen Wert true. Diese Hashes können mit dem in Spring Security integrierten BCrypt-Encoder generiert werden.

Warum sollten wir Passwörter hashen?


Wir haben alle von jüngsten Cyber-Angriffen gehört, die dazu geführt haben, dass große Unternehmen Passwörter gestohlen haben. Warum wird empfohlen, unsere Passwörter nur nach dem Hacken zu ändern? Weil diese großen Unternehmen dafür gesorgt haben, dass Passwörter immer in ihren Datenbanken gehasht werden!

Obwohl es sich immer lohnt, Kennwörter nach solchen Datenhacks zu ändern, macht es das Kennwort-Hashing äußerst schwierig, das tatsächliche Kennwort des Benutzers zu finden, da es sich um einen Einwegalgorithmus handelt. In der Tat kann es dauern Jahre ein komplexes Passwort richtig gehasht zu knacken. Dies bietet einen zusätzlichen Schutz gegen Passwortdiebstahl. Und Spring Security vereinfacht das Hashing, daher sollte die eigentliche Frage lauten: "Warum nicht?"

Hinzufügen eines Benutzers zu MongoDB


Ich werde ein Minimum an Feldern hinzufügen, die für meine Sammlung users(Benutzer) erforderlich sind , sodass ein Dokument mit Benutzern in meiner Datenbank nur username(Benutzername) und gehashtes BCrypt password(Kennwort) enthält. In diesem Beispiel lautet mein Benutzername adminund mein Kennwort. welcome1Ich würde jedoch vorschlagen, einen stabileren Benutzernamen und ein stabileres Kennwort in der API auf Produktionsebene zu verwenden.

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

Dies sind alle Einstellungen, die in MongoDB benötigt werden! Der Rest der Konfiguration erfolgt in unserem Java-Code.

Hinzufügen eines Benutzermodells und eines Repositorys


Der vorherige Beitrag wurde ausführlich über die Modelle und Repositories von Mongo beschrieben, daher werde ich hier nicht näher darauf eingehen, wie sie funktionieren. Wenn Sie Ihr Wissen auffrischen möchten, zögern Sie nicht, meinen vorherigen Beitrag zu besuchen!

Der Nachteil ist, dass Spring wissen muss, wie das Dokument user(Modell) aussehen wird und wie auf die Sammlung userin der Datenbank (Repositorys) zugegriffen werden kann. Wir können diese Dateien in denselben Ordnern von Modellen bzw. Repositorys ablegen wie in der vorherigen Übung.

Modell


Das Modell wird eine Java - Basisklasse mit benutzerdefinierten sein _id, usernameund password. Die Datei wird benannt Users.java. und wird so aussehen:

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; }
}

Repository


Das Repository wird aufgerufen UsersRepository.javaund sieht folgendermaßen aus. Denken Sie daran, dass wir Benutzer anhand dieser finden müssen username, sodass wir die Methode findByUsernamein die Repository-Oberfläche aufnehmen müssen.

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);
}

Und das wars für das Modell und das Repository!

Hinzufügen von Sicherheitsabhängigkeiten


Im Stammverzeichnis des Projekts sollte sich eine Datei mit einem Namen befinden pom.xml. Wir haben diese Datei noch nicht berührt, aber die POM-Datei enthält alle Abhängigkeiten unseres Projekts, und wir werden ein paar hinzufügen. Beginnen wir also damit, diese Datei zu öffnen und zum Tag zu scrollen . Die einzige neue Abhängigkeit, die wir brauchen, ist die Spring-Starter-Sicherheit . Spring verfügt über einen integrierten Versionsmanager. Die Abhängigkeit, die wir dem Tag hinzufügen müssen, lautet also wie folgt:<dependencies>

<dependencies>

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

Und Maven wird die Quelldateien für uns herunterladen, also müssen unsere Abhängigkeiten bereit sein!

Authentifizierungsdienst erstellen


Wir müssen Spring mitteilen, wo sich unsere Benutzerdaten befinden und wo die für die Authentifizierung erforderlichen Informationen zu finden sind. Zu diesem Zweck können wir einen Authentifizierungsdienst (Authentifizierungsdienst) erstellen. Beginnen wir mit dem Erstellen eines neuen Ordners in src/main/resources/java/[package name]aufgerufenen Diensten, und wir können in diesem Konfigurationsordner eine neue Datei mit dem Namen erstellen MongoUserDetailsService.java.

MongoUserDetailsService.java


Diese Klasse hat eine Hauptkomponente, daher gebe ich hier nur die gesamte Klasse und erkläre sie unten:

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);
  }
}

Dieses Snippet beginnt mit den Importen, die wir in der Datei benötigen. Außerdem gibt der Abschnitt implements UserDetailsServicean, dass diese Klasse einen Dienst zum Suchen und Authentifizieren von Benutzern erstellt. Die Anmerkung @Componentgibt dann an, dass diese Klasse in eine andere Datei eingebettet werden kann (z. B. die SecurityConfiguration-Datei, die in mehreren Abschnitten behandelt wird).

Anmerkung @Autowiredvorbei private UsersRepository repository; Als Beispiel für die Implementierung bietet uns diese Eigenschaft eine Instanz von uns UsersRepositoryfür die Arbeit. Anmerkung @Overridegibt an, dass diese Methode anstelle der Standardmethode UserDetailsService verwendet wird. Zunächst Usersruft diese Methode das Objekt mithilfe der von findByUsernameuns deklarierten Methode aus der MongoDB-Datenquelle ab UsersRepository.

Die Methode prüft dann, ob der Benutzer gefunden wurde oder nicht. Anschließend werden dem Benutzer Berechtigungen / Rollen erteilt (dies kann zusätzliche Authentifizierungsebenen für Zugriffsebenen hinzufügen, für diese Lektion reicht jedoch eine Rolle aus). Schließlich kehrt das Verfahren ein Spring - Objekt Usermit username, passwordund die roleauthentifizierten Benutzer.

Sicherheitskonfiguration erstellen


Wir müssen einige der in Spring integrierten Sicherheitsprotokolle neu definieren, um unseren Datenbank- und Hash-Algorithmus verwenden zu können. Daher benötigen wir eine spezielle Konfigurationsdatei. Um es zu erstellen, müssen wir einen neuen Ordner src/main/resources/java/[package name]mit dem Namen configerstellen, und wir müssen auch eine neue Datei in diesem Konfigurationsordner mit dem Namen erstellen SecurityConfiguration.java. Diese Datei besteht aus mehreren wichtigen Teilen. Beginnen wir also mit der SecurityConfiguration-Basisklasse:

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;
}

Es gibt bereits genug zu erledigen, also fangen wir von oben an. Eine Anmerkung @Configurationgibt an, dass die Klasse Java-Beans enthält, die hier ausführlich beschrieben werden. Eine Anmerkung @EnableConfigurationPropertiesgibt an, was die Klasse als spezielle Konfigurations-Bean enthalten wird. Anschließend ordnet die Anweisung extends WebSecurityConfigurerAdapterdie übergeordnete Klasse WebSecurityConfigurerAdapterunserer Konfigurationsklasse zu und stellt unserer Klasse alles zur Verfügung, was erforderlich ist, um sicherzustellen, dass die Sicherheitsregeln eingehalten werden. Schließlich fügt die Klasse automatisch eine Instanz ( @Autowired) ein MongoUserDetailsService, die wir später in dieser Datei verwenden können.

Authentifizierungsschritt


Als Nächstes müssen wir Spring Security mitteilen, wie die Benutzerauthentifizierung behandelt werden soll. Standardmäßig verfügt Spring Security über einen vordefinierten Benutzernamen und ein vordefiniertes Kennwort, CSRF-Schutz und Sitzungsverwaltung . Wir möchten jedoch, dass unsere Benutzer ihren Benutzernamen und ihr Passwort verwenden, um auf die Datenbank zuzugreifen. Da unsere Benutzer bei jeder Anforderung erneut authentifiziert werden, anstatt sich anzumelden, benötigen wir keinen CSRF-Schutz und keine Sitzungsverwaltung. Daher können wir eine Methode mit einem Namen hinzufügen configure, der das Standardauthentifizierungsschema überschreibt, um Spring genau mitzuteilen, wie Wir möchten die Authentifizierung handhaben und sehen folgendermaßen aus:

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

Auch hier passiert eine Menge, also werden wir es schrittweise klären. Die Anmerkung @Overrideweist Spring Boot an, die Methode configure (HttpSecurity http)anstelle der Standardkonfiguration von Spring zu verwenden. Dann rufen wir eine Reihe von Methoden für das Objekt auf, httpin dem die eigentliche Konfiguration stattfindet. Diese Methoden führen Folgendes aus:

  • csrf().disable(): Deaktiviert den CSRF-Schutz, da er für die API nicht benötigt wird
  • authorizeRequests().anyRequest().authenticated(): Erklärt, dass alle Anforderungen an einen Endpunkt autorisiert werden müssen, andernfalls müssen sie abgelehnt werden.
  • and().httpBasic(): SpringDamit wird eine grundlegende HTTP-Authentifizierung erwartet (siehe oben).
  • .and().sessionManagement().disable(): Weist Spring an, keine Sitzungsinformationen für Benutzer zu speichern, da dies für die API nicht erforderlich ist

Hinzufügen eines Bcrypt-Encoders


Jetzt müssen wir Spring anweisen, den BCrypt-Encoder zum Hashing und Vergleichen von Passwörtern zu verwenden - das klingt nach einer schwierigen Aufgabe, ist aber in der Tat sehr einfach. Wir können diesen Encoder hinzufügen, indem wir unserer Klasse einfach die folgenden Zeilen hinzufügen SecurityConfiguration:

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

Und die ganze Arbeit! Diese einfache Bean teilt Spring mit, dass der PasswordEncoder, den wir verwenden möchten, Spring Boot ist BCryptPasswordEncoder(), um Kennwort-Hashes zu codieren und zu vergleichen. Spring Boot enthält auch mehrere andere Passwort-Encoder - ich empfehle, sie auszuprobieren, wenn Sie experimentieren möchten!

Geben Sie den Authentifizierungsmanager an


Schließlich müssen wir in unserem angeben, SecurityConfigurationdass wir MongoUserDetailsService(die wir im vorherigen Abschnitt erstellt haben) für unsere Authentifizierung verwenden möchten . Wir können dies mit der folgenden Methode tun:

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

Diese Methode überschreibt einfach die Standardkonfiguration AuthenticationManagerBuilderund ersetzt stattdessen unseren eigenen benutzerdefinierten Datenübertragungsdienst.

Endgültige SecurityConfiguration.java-Datei


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);
 }
}

Authentifizierungsprüfung


Ich werde eine Schnellanfrage GETmit gültiger und falscher Authentifizierung testen , um sicherzustellen, dass die Konfiguration wie geplant funktioniert.

Falscher Benutzername / Passwort

URL: http://localhost:8080/pets/
Methode: GET
Login: Basic YWRtaW46d2VsY29tZQ==

Antwort:

401 Unauthorized

Gültiger Benutzername / Passwort

URL: http://localhost:8080/pets/
Methode: GET
Login: Basic YWRtaW46d2VsY29tZTE=

Antwort:

[
 {
   “_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”
 }
]

Fazit


Es funktioniert wie es sollte! Die grundlegende HTTP-Authentifizierung für die Spring Boot-API kann schwierig sein, aber hoffentlich hilft dieses Handbuch dabei, sie verständlicher zu machen. Die Authentifizierung ist im heutigen Cyber-Klima ein Muss. Daher sind Tools wie Spring Security von entscheidender Bedeutung, um die Integrität und Sicherheit Ihrer Daten zu gewährleisten.

Das ist alles! Triff mich auf dem Kurs .

All Articles