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 befassenEinfü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@55w0Rd
in 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@55w0Rd
mit BCrypt gehashtes Kennwort eine Bedeutung haben $2b$10$Qrc6rGzIGaHpbgPM5kVXdeNZ9NiyRWC69Wk/17mttHKnDR2lW49KS
. Beim Aufrufen der matches
BCrypt- 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 admin
und mein Kennwort. welcome1
Ich 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 user
in 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
, username
und 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.java
und sieht folgendermaßen aus. Denken Sie daran, dass wir Benutzer anhand dieser finden müssen username
, sodass wir die Methode findByUsername
in 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 UserDetailsService
an, dass diese Klasse einen Dienst zum Suchen und Authentifizieren von Benutzern erstellt. Die Anmerkung @Component
gibt dann an, dass diese Klasse in eine andere Datei eingebettet werden kann (z. B. die SecurityConfiguration-Datei, die in mehreren Abschnitten behandelt wird).Anmerkung @Autowired
vorbei private UsersRepository repository
; Als Beispiel für die Implementierung bietet uns diese Eigenschaft eine Instanz von uns UsersRepository
für die Arbeit. Anmerkung @Override
gibt an, dass diese Methode anstelle der Standardmethode UserDetailsService verwendet wird. Zunächst Users
ruft diese Methode das Objekt mithilfe der von findByUsername
uns 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 User
mit username
, password
und die role
authentifizierten 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 config
erstellen, 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 @Configuration
gibt an, dass die Klasse Java-Beans enthält, die hier ausführlich beschrieben werden. Eine Anmerkung @EnableConfigurationProperties
gibt an, was die Klasse als spezielle Konfigurations-Bean enthalten wird. Anschließend ordnet die Anweisung extends WebSecurityConfigurerAdapter
die übergeordnete Klasse WebSecurityConfigurerAdapter
unserer 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 @Override
weist 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, http
in 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 wirdauthorizeRequests().anyRequest().authenticated()
: Erklärt, dass alle Anforderungen an einen Endpunkt autorisiert werden müssen, andernfalls müssen sie abgelehnt werden.and().httpBasic(): Spring
Damit 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, SecurityConfiguration
dass 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 AuthenticationManagerBuilder
und 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 GET
mit gültiger und falscher Authentifizierung testen , um sicherzustellen, dass die Konfiguration wie geplant funktioniert.Falscher Benutzername / PasswortURL: http://localhost:8080/pets/
Methode: GET
Login: Basic YWRtaW46d2VsY29tZQ==
Antwort:401 Unauthorized
Gültiger Benutzername / PasswortURL: 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 .