So richten Sie die JWT-Autorisierung und -Authentifizierung für Java Spring Boot ein

Im letzten Monat hatte ich die Möglichkeit, die JWT-Authentifizierung für ein Nebenprojekt zu implementieren. Ich habe zuvor mit JWT in Ruby on Rails gearbeitet, aber dies war mein erstes Mal im Frühjahr.

In diesem Beitrag werde ich versuchen zu erklären, was ich in meinem Projekt gelernt und angewendet habe, um meine Erfahrungen zu teilen und hoffentlich einigen Menschen zu helfen.

Wir werden zunächst einen kurzen Blick auf die Theorie hinter JWT werfen und wie sie funktioniert. Dann werden wir uns ansehen, wie es in einer Spring Boot-Anwendung implementiert wird.

JWT-Grundlagen

JWT oder JSON Web Tokens (RFC 7519) ist ein Standard, der hauptsächlich zum Sichern von REST-APIs verwendet wird. Obwohl es sich um eine relativ neue Technologie handelt, gewinnt sie rasch an Popularität.

Beim JWT-Authentifizierungsprozess sendet das Front-End (Client) zunächst einige Anmeldeinformationen, um sich selbst zu authentifizieren (Benutzername und Kennwort in unserem Fall, da wir an einer Webanwendung arbeiten).

Der Server (in unserem Fall die Spring-App) überprüft dann diese Anmeldeinformationen. Wenn sie gültig sind, generiert er eine JWT und gibt sie zurück.

Nach diesem Schritt muss der Client dieses Token im Autorisierungsheader der Anforderung im Formular "Bearer TOKEN" bereitstellen. Das Back-End überprüft die Gültigkeit dieses Tokens und autorisiert oder lehnt Anforderungen ab. Das Token kann auch Benutzerrollen speichern und die Anforderungen basierend auf den angegebenen Berechtigungen autorisieren.

Implementierung

Nun wollen wir sehen, wie wir den JWT-Anmelde- und Speichermechanismus in einer echten Spring-Anwendung implementieren können.

Abhängigkeiten

Unten sehen Sie die Liste der Maven-Abhängigkeiten, die unser Beispielcode verwendet. Beachten Sie, dass die Kernabhängigkeiten wie Spring Boot und Hibernate in diesem Screenshot nicht enthalten sind.

Benutzer speichern

Zunächst erstellen wir Controller, um Benutzer sicher zu speichern und anhand des Benutzernamens und des Kennworts zu authentifizieren.

Wir haben eine Modellentität namens Benutzer. Es ist eine einfache Entitätsklasse, die der USER- Tabelle zugeordnet ist. Sie können je nach Anwendung alle gewünschten Eigenschaften verwenden.

Wir haben auch eine einfache UserRepository- Klasse, um Benutzer zu speichern. Wir müssen die findByUsername- Methode überschreiben , da wir sie bei der Authentifizierung verwenden werden.

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

Wir sollten niemals Klartextkennwörter in der Datenbank speichern, da viele Benutzer dazu neigen, dasselbe Kennwort für mehrere Websites zu verwenden.

Es gibt viele verschiedene Hashing-Algorithmen, aber der am häufigsten verwendete ist BCrypt und eine empfohlene Methode für sicheres Hashing. In diesem Artikel finden Sie weitere Informationen zum Thema.

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

Um das Passwort zu hashen , definieren wir eine BCrypt- Bean in @SpringBootApplication und kommentieren die Hauptklasse wie folgt:

Wir werden die Methoden für diese Bean aufrufen, wenn wir ein Passwort hashen müssen.

Wir benötigen auch einen UserController, um Benutzer zu speichern. Wir erstellen den Controller, kommentieren ihn mit @RestController und definieren das entsprechende Mapping.

In unserer Anwendung speichern wir den Benutzer basierend auf einem DTO-Objekt, das vom Frontend übergeben wird. Sie können auch ein Benutzerobjekt in @RequestBody übergeben .

Nachdem wir das DTO-Objekt übergeben haben, verschlüsseln wir das Kennwortfeld mit der zuvor erstellten BCrypt- Bean. Sie können dies auch in der Steuerung tun, es ist jedoch besser, diese Logik in die Serviceklasse aufzunehmen.

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

Authentifizierungsfilter

Wir benötigen eine Authentifizierung, um sicherzustellen, dass der Benutzer wirklich der ist, für den er sich ausgibt. Wir werden das klassische Paar aus Benutzername und Passwort verwenden, um dies zu erreichen.

Hier sind die Schritte zum Implementieren der Authentifizierung:

  1. Erstellen Sie unseren Authentifizierungsfilter, der UsernamePasswordAuthenticationFilter erweitert
  2. Erstellen Sie eine Sicherheitskonfigurationsklasse, die WebSecurityConfigurerAdapter erweitert, und wenden Sie den Filter an

Hier ist der Code für unseren Authentifizierungsfilter - wie Sie vielleicht wissen, sind Filter das Rückgrat von Spring Security.

Lassen Sie uns diesen Code Schritt für Schritt durchgehen.

Diese Klasse erweitert UsernamePasswordAuthenticationFilter , die Standardklasse für die Kennwortauthentifizierung in Spring Security. Wir erweitern es, um unsere benutzerdefinierte Authentifizierungslogik zu definieren.

Wir rufen die Methode setFilterProcessesUrl in unserem Konstruktor auf. Diese Methode setzt die Standard-Anmelde-URL auf den angegebenen Parameter.

Wenn Sie diese Zeile entfernen, erstellt Spring Security standardmäßig den Endpunkt "/ login" . Es definiert den Anmeldeendpunkt für uns, weshalb wir keinen Anmeldeendpunkt in unserem Controller explizit definieren.

Nach dieser Zeile lautet unser Anmeldeendpunkt / api / services / controller / user / login . Mit dieser Funktion können Sie mit Ihren Endpunkten konsistent bleiben.

Wir überschreiben die attemptAuthentication und successfulAuthentication Methoden der UsernameAuthenticationFilter Klasse.

Die Funktion tryAuthentication wird ausgeführt, wenn der Benutzer versucht, sich bei unserer Anwendung anzumelden. Es liest die Anmeldeinformationen, erstellt daraus ein Benutzer-POJO und überprüft dann die Anmeldeinformationen zur Authentifizierung.

Wir übergeben den Benutzernamen, das Passwort und eine leere Liste. Die leere Liste stellt die Berechtigungen (Rollen) dar und wir lassen sie unverändert, da wir noch keine Rollen in unserer Anwendung haben.

Wenn die Authentifizierung erfolgreich ist, das successfulAuthentication läuft Methode. Die Parameter dieser Methode werden von Spring Security hinter den Kulissen übergeben.

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. :)