Heim  >  Artikel  >  Java  >  Implementierung der tokenbasierten Authentifizierung in Spring Boot mithilfe von Spring Security, JWT und JDBC-Vorlage

Implementierung der tokenbasierten Authentifizierung in Spring Boot mithilfe von Spring Security, JWT und JDBC-Vorlage

Susan Sarandon
Susan SarandonOriginal
2024-10-29 02:22:301019Durchsuche

Github-Link für die Code-Referenz

Einführung

In modernen Webanwendungen ist eine sichere Benutzerauthentifizierung von entscheidender Bedeutung. Traditionell ist die sitzungsbasierte Authentifizierung weit verbreitet, aber da Anwendungen immer verteilter und skalierbarer werden, bietet die tokenbasierte Authentifizierung mehrere Vorteile.

Durch die tokenbasierte Authentifizierung können Anwendungen zustandslos sein, was bedeutet, dass der Server keine Sitzungsdaten speichern muss, wodurch er sich gut für skalierbare RESTful-APIs eignet.

Dieses Tutorial führt Sie durch die Implementierung der JWT-Authentifizierung (JSON Web Token) in einer Spring Boot-Anwendung unter Verwendung von Spring Security mit JDBC-Vorlage.

JWT (JSON Web Token) ist eine kompakte, URL-sichere Methode zur Darstellung von Ansprüchen, die zwischen zwei Parteien übertragen werden. Es wird häufig für die zustandslose Authentifizierung verwendet, bei der jede Anfrage mithilfe eines signierten Tokens authentifiziert wird.

Warum JWT und tokenbasierte Authentifizierung?

Zustandslose Authentifizierung
JWT-Token sind eigenständig und übertragen die Authentifizierungsinformationen des Benutzers direkt in der Token-Nutzlast, was die Speichernutzung des Servers reduziert und die Skalierbarkeit erhöht.

Plattformübergreifende Unterstützung
Token sind in Mobil- und Webanwendungen einfach zu verwenden, da sie sicher im Client gespeichert werden können (z. B. im lokalen Speicher oder in Cookies).

Sicherheit
Jeder Token ist digital signiert, was seine Integrität gewährleistet und es dem Server ermöglicht, ihn zu überprüfen, ohne bei jeder Anfrage eine Datenbank abzufragen.

Was Sie lernen werden

In diesem Tutorial erfahren Sie, wie Sie:

  1. Richten Sie eine Spring Boot-Anwendung mit Spring Security ein.
  2. Implementieren Sie die JWT-Token-basierte Authentifizierung mithilfe der JDBC-Vorlage, um Benutzer zu verwalten und Aktualisierungstokens sicher zu speichern.
  3. Richten Sie Endpunkte für die Anmeldung, die Generierung von Zugriffstoken und die Handhabung von Aktualisierungstoken ein.

Am Ende dieses Tutorials verfügen Sie über ein sicheres, zustandsloses Authentifizierungssystem, das Spring Boot und JWT nutzt, um eine nahtlose und skalierbare Zugriffskontrolle für Ihre Anwendungen bereitzustellen.


API-Ablauf:

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template


Technologische Anforderungen:

  • Java 17 / 11 / 8
  • Spring Boot 3/2 (mit Spring Security, Spring Web)
  • jjwt-api 0.11.5
  • PostgreSQL/MySQL
  • Maven

1. Richten Sie Ihr Spring Boot-Projekt ein

Verwenden Sie das Spring-Webtool oder Ihr Entwicklungstool (STS, Intellij oder eine beliebige IDE), um ein Spring Boot-Projekt zu erstellen.

öffnen Sie pom.xml und fügen Sie Abhängigkeiten für Spring Security, JWT und JDBC-Vorlage hinzu:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

2. Konfigurieren Sie Datenbank- und App-Eigenschaften

Öffnen Sie im Ordner src/main/resources application.properties und fügen Sie die folgenden Konfigurationen hinzu. Ich werde für dieses Tutorial die Postgres-Datenbank verwenden.

spring.application.name= authmanager

server.port= 1001
servlet.context-path= /authmanager

database.username= postgres 
database.password= admin@123
database.driverClassName= org.postgresql.Driver
database.jdbcUrl= jdbc:postgresql://localhost:5432/postgres
database.maxActive= 5
database.minIdle= 5
database.poolName= Authmanager Postgres Datasource

app.jwtSecret= ###############ib-Spring###############
app.jwtExpirationMs= 3600000
app.jwtRefreshExpirationMs= 86400000

3. Richten Sie Datenbanktabellen ein

Definieren Sie eine einfache Tabellenstruktur für Benutzerinformationen, Rollen, Benutzerrollenzuordnung und Aktualisierungstoken:

CREATE SCHEMA IB;
-------------------------------------------------------------------------

create sequence users_uniqueid_seq START 1;

create table ib.users(
        uniqueid bigint not null default nextval('users_uniqueid_seq') PRIMARY KEY,
        email varchar(75),
        password varchar(200),
        username varchar(20)
);

insert into ib.users(email,password,username) values ('admin@ib.com','a$VcdzH8Q.o4KEo6df.XesdOmXdXQwT5ugNQvu1Pl0390rmfOeA1bhS','admin');

#(password = 12345678)
-------------------------------------------------------------------------

create sequence roles_id_seq START 1;

create table ib.roles(
        id int not null default nextval('roles_id_seq') PRIMARY KEY,
        name varchar(20)
);

INSERT INTO ib.roles(name) VALUES('ROLE_USER');
INSERT INTO ib.roles(name) VALUES('ROLE_MODERATOR');
INSERT INTO ib.roles(name) VALUES('ROLE_ADMIN');

-------------------------------------------------------------------------

create table ib.user_roles(
        user_uniqueid bigint not null,
        role_id int not null,
        primary key(user_uniqueid,role_id)
);
insert into ib.user_roles (user_uniqueid,role_id) values (1,3);
-------------------------------------------------------------------------

create sequence refresh_tokens_id_seq START 1;

create table ib.refresh_tokens(
        id bigint not null default nextval('refresh_tokens_id_seq') PRIMARY KEY,
        uniqueid bigint,
        token varchar(500) not null,
        expiryDate TIMESTAMP WITH TIME ZONE not null
);

-------------------------------------------------------------------------

4. Erstellen Sie die Modelle

Lassen Sie uns die folgenden Modelle definieren.
Erstellen Sie im Modellpaket die folgenden 4 Dateien:

model/ERole.java

package com.security.authmanager.model;

public enum ERole {
    ROLE_USER,
    ROLE_MODERATOR,
    ROLE_ADMIN
}

model/Role.java

package com.security.authmanager.model;

public class Role {
    private Integer id;
    private ERole name;

    public Role() {
    }

    public Role(ERole name) {
        this.name = name;
    }

    //generate getters and setters
}

model/User.java

package com.security.authmanager.model;

import java.util.HashSet;
import java.util.Set;

public class User {
    private Long id;
    private String username;
    private String email;
    private String password;

    private Set<Role> roles = new HashSet<>();

    public User() {
    }

    public User(String username, String email, String password) {
        this.username = username;
        this.email = email;
        this.password = password;
    }

    //generate getters and setters
}

model/RefreshToken.java

package com.security.authmanager.model;

import java.util.HashSet;
import java.util.Set;

public class User {
    private Long id;
    private String username;
    private String email;
    private String password;

    private Set<Role> roles = new HashSet<>();

    public User() {
    }

    public User(String username, String email, String password) {
        this.username = username;
        this.email = email;
        this.password = password;
    }

    //generate getters and setters
}

5. Implementieren Sie die CustomUserDetailsRepository-Klasse

Die CustomUserDetailsRepository-Klasse ist ein Spring @Repository, das benutzerdefinierte Datenbankoperationen im Zusammenhang mit Benutzer- und Rollenentitäten verarbeitet. Es verwendet JdbcTemplate, um SQL-Abfragen für Aufgaben wie das Abrufen von Benutzern, das Überprüfen, ob ein Benutzer anhand seines Benutzernamens oder seiner E-Mail-Adresse existiert, das Erstellen neuer Benutzer und das Abrufen von Rollen auszuführen.

package com.security.authmanager.repository;

import com.security.authmanager.common.QueryConstants;
import com.security.authmanager.model.ERole;
import com.security.authmanager.model.Role;
import com.security.authmanager.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

@Repository
public class CustomUserDetailsRepository {

    private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsRepository.class);
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public User fetchUserByUserName(String userName){
        try{
            return jdbcTemplate.query((conn) ->{
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.FETCH_USER);
                ps.setString(1, userName.toUpperCase());
                return ps;
            },rs->{
                User user = null;
                Set<Role> roles = new HashSet<>();
                while (rs.next()) {
                    if (user == null) {
                        user = new User();
                        user.setEmail(rs.getString("email"));
                        user.setId(rs.getLong("uniqueid"));
                        user.setPassword(rs.getString("password"));
                        user.setUsername(rs.getString("username"));
                    }
                    Role role = new Role();
                    role.setId(rs.getInt("id"));
                    role.setName(ERole.valueOf(rs.getString("name")));
                    roles.add(role);
                }
                if (user != null) {
                    user.setRoles(roles);
                }
                return user;
            });
        }catch(Exception e){
            logger.error("Exception in fetchUserByUserName()",e);
            throw new RuntimeException(e);
        }
    }

    public boolean existsByUsername(String userName) {
        try{
              return jdbcTemplate.query((conn) -> {
                 final PreparedStatement ps = conn.prepareStatement(QueryConstants.CHECK_USER_BY_USERNAME);
                 ps.setString(1, userName.toUpperCase());
                 return ps;
             }, (rs,rownum) -> rs.getInt("count")).get(0)>0;
        }catch(Exception e){
            logger.error("Exception in existsByUsername()",e);
            throw new RuntimeException(e);
        }
    }

    public boolean existsByEmail(String email) {
        try{
            return jdbcTemplate.query((conn) -> {
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.CHECK_USER_BY_EMAIL);
                ps.setString(1, email.toUpperCase());
                return ps;
            }, (rs,rownum) -> rs.getInt("count")).get(0)>0;
        }catch(Exception e){
            logger.error("Exception in existsByEmail()",e);
            throw new RuntimeException(e);
        }
    }

    public Role findRoleByName(ERole eRole) {
        try{
            return jdbcTemplate.query((conn) -> {
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.FETCH_ROLE_BY_NAME);
                ps.setString(1, String.valueOf(eRole));
                return ps;
            }, rs -> {
                Role role=null;
                while(rs.next()){
                    role = new Role();
                    role.setName(ERole.valueOf(rs.getString("name")));
                    role.setId(rs.getInt("id"));
                }
                return role;
            });
        }catch(Exception e){
            logger.error("Exception in findRoleByName()",e);
            throw new RuntimeException(e);
        }
    }

    public void createUser(User user) {
        try(Connection conn = Objects.requireNonNull(jdbcTemplate.getDataSource()).getConnection()){
            try (PreparedStatement userStatement = conn.prepareStatement(QueryConstants.INSERT_TO_USERS,Statement.RETURN_GENERATED_KEYS)) {
                userStatement.setString(1, user.getEmail().toUpperCase());
                userStatement.setString(2, user.getPassword());
                userStatement.setString(3, user.getUsername().toUpperCase());
                userStatement.executeUpdate();
                // Retrieve generated userId
                try (ResultSet generatedKeys = userStatement.getGeneratedKeys()) {
                    if (generatedKeys.next()) {
                        Long userId = generatedKeys.getLong(1); // Assuming userId is of type VARCHAR
                        logger.info("gen userid {}",userId.toString());
                        user.setId(userId);
                    }
                }
            }
            if (user.getRoles() != null && !user.getRoles().isEmpty()) {
                try (PreparedStatement userRoleStatement = conn.prepareStatement(QueryConstants.INSERT_TO_USER_ROLES)) {
                    for (Role role : user.getRoles()) {
                        userRoleStatement.setLong(1, user.getId());
                        userRoleStatement.setLong(2, role.getId());
                        userRoleStatement.executeUpdate();
                    }
                }
            }
        }catch(Exception e){
            logger.error("Exception in existsByEmail()",e);
            throw new RuntimeException(e);
        }
    }
}

Dieses Repository führt benutzerdefinierte SQL-basierte CRUD-Operationen zur Verwaltung von Benutzer- und Rollendaten in der Datenbank aus.

Schlüsselfunktionen:

  • Benutzer nach Benutzernamen abrufen und zugehörige Rollen abrufen.
  • Überprüfen Sie anhand des Benutzernamens oder der E-Mail-Adresse, ob ein Benutzer vorhanden ist.
  • Fügen Sie neue Benutzer und ihre Rollen in die Datenbank ein.
  • Rufen Sie Rollendetails basierend auf Rollennamen ab.

6. Implementieren Sie die RefreshTokenRepository-Klasse

Die RefreshTokenRepository-Klasse ist ein Spring @Repository, das Datenbankoperationen im Zusammenhang mit der RefreshToken-Entität verarbeitet. Es verwendet Springs JdbcTemplate für die Interaktion mit der Datenbank über unformatierte SQL-Abfragen und kapselt die Logik zum Speichern, Löschen und Abrufen von Aktualisierungstokens.

package com.security.authmanager.repository;

import com.security.authmanager.common.QueryConstants;
import com.security.authmanager.model.ERole;
import com.security.authmanager.model.RefreshToken;
import com.security.authmanager.model.Role;
import com.security.authmanager.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.util.Optional;

@Repository
public class RefreshTokenRepository {
    private static final Logger logger = LoggerFactory.getLogger(RefreshTokenRepository.class);
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void deleteRefreshToken(RefreshToken refreshToken) {
        try{
            jdbcTemplate.update(QueryConstants.DELETE_REFRESH_TOKEN,(final PreparedStatement ps) ->{
                ps.setString(1,refreshToken.getToken());
            });
        }catch (Exception e){
            logger.error("Exception in deleteRefreshToken()",e);
            throw new RuntimeException(e);
        }
    }

    public int deleteRefreshTokenByUser(User user) {
        return 0;
    }

    public RefreshToken saveRefreshToken(RefreshToken refreshToken) {
        try{
            jdbcTemplate.update(QueryConstants.SAVE_REFRESH_TOKEN,(final PreparedStatement ps) ->{
                ps.setLong(1,refreshToken.getUser().getId());
                ps.setString(2,refreshToken.getToken());
                ps.setTimestamp(3, Timestamp.from(refreshToken.getExpiryDate()));
            });
        }catch (Exception e){
            logger.error("Exception in saveRefreshToken()",e);
            throw new RuntimeException(e);
        }
        return refreshToken;
    }

    public Optional<RefreshToken> findByToken(String token) {
        RefreshToken refreshToken = new RefreshToken();
        try{
            return Optional.ofNullable(jdbcTemplate.query((conn) -> {
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.FIND_BY_TOKEN);
                ps.setString(1, token);
                return ps;
            }, rs -> {
                User user = new User();
                while (rs.next()) {
                    refreshToken.setId(rs.getLong("id"));
                    refreshToken.setToken(rs.getString("token"));
                    refreshToken.setExpiryDate(rs.getTimestamp("expiryDate").toInstant());
                    user.setId(rs.getLong("uniqueid"));
                    user.setEmail(rs.getString("email"));
                    user.setUsername(rs.getString("username"));
                    refreshToken.setUser(user);
                }
                return refreshToken;
            }));
        }catch(Exception e){
            logger.error("Exception in findByToken()",e);
            throw new RuntimeException(e);
        }
    }
}

Dieses Repository interagiert direkt mit der Datenbank für CRUD-Vorgänge für RefreshToken-Entitäten.

Schlüsselfunktionen:

  • Aktualisierungstoken speichern.
  • Aktualisierungstoken nach Tokenwert abrufen.
  • Aktualisierungstoken entweder nach Token oder Benutzer löschen.

7. Konfigurieren Sie Spring Security

  • WebSecurityConfig konfiguriert Spring Security für die Verwendung der JWT-basierten Token-Authentifizierung.

  • Es definiert Beans für verschiedene Komponenten, die für die Authentifizierung benötigt werden, wie AuthTokenFilter (zur Handhabung von JWT-Tokens), DaoAuthenticationProvider (zum Abrufen von Benutzerdetails und zur Validierung von Passwörtern) und BCryptPasswordEncoder (zum Hashing und Vergleichen von Passwörtern).

Die SecurityFilterChain konfiguriert, wie eingehende HTTP-Anfragen gesichert werden:
- Ermöglicht den Zugriff auf bestimmte öffentliche Routen (/auth/**, /test/**).
- Schützt alle anderen Routen, die eine Authentifizierung erfordern.
– Deaktiviert die Sitzungsverwaltung (macht das System zustandslos).
– Konfiguriert einen Filter zum Abfangen und Verarbeiten von JWT-Tokens für
Benutzerauthentifizierung.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

8. Implementieren Sie UserDetailsImpl

Diese Klasse wird in Spring Security hauptsächlich verwendet, um den aktuell authentifizierten Benutzer darzustellen.

Wenn ein Benutzer versucht, sich anzumelden:

  1. Spring Security ruft zum Laden UserDetailsService.loadUserByUsername() auf den Benutzer aus der Datenbank.
  2. Es wird eine Instanz von UserDetailsImpl mit den Benutzerdetails erstellt (einschließlich ihrer Rollen).
  3. Dieses UserDetailsImpl-Objekt wird dann von Spring Security verwendet, um Authentifizieren Sie den Benutzer und überprüfen Sie seine Berechtigungen während des gesamten Vorgangs Sitzung.

UserDetailsImpl ist eine Brücke zwischen Ihrer Benutzerentität und den internen Mechanismen von Spring Security zur Authentifizierung und Autorisierung.

spring.application.name= authmanager

server.port= 1001
servlet.context-path= /authmanager

database.username= postgres 
database.password= admin@123
database.driverClassName= org.postgresql.Driver
database.jdbcUrl= jdbc:postgresql://localhost:5432/postgres
database.maxActive= 5
database.minIdle= 5
database.poolName= Authmanager Postgres Datasource

app.jwtSecret= ###############ib-Spring###############
app.jwtExpirationMs= 3600000
app.jwtRefreshExpirationMs= 86400000

Unten sind die Abfragen: (QueryConstants.java)

CREATE SCHEMA IB;
-------------------------------------------------------------------------

create sequence users_uniqueid_seq START 1;

create table ib.users(
        uniqueid bigint not null default nextval('users_uniqueid_seq') PRIMARY KEY,
        email varchar(75),
        password varchar(200),
        username varchar(20)
);

insert into ib.users(email,password,username) values ('admin@ib.com','a$VcdzH8Q.o4KEo6df.XesdOmXdXQwT5ugNQvu1Pl0390rmfOeA1bhS','admin');

#(password = 12345678)
-------------------------------------------------------------------------

create sequence roles_id_seq START 1;

create table ib.roles(
        id int not null default nextval('roles_id_seq') PRIMARY KEY,
        name varchar(20)
);

INSERT INTO ib.roles(name) VALUES('ROLE_USER');
INSERT INTO ib.roles(name) VALUES('ROLE_MODERATOR');
INSERT INTO ib.roles(name) VALUES('ROLE_ADMIN');

-------------------------------------------------------------------------

create table ib.user_roles(
        user_uniqueid bigint not null,
        role_id int not null,
        primary key(user_uniqueid,role_id)
);
insert into ib.user_roles (user_uniqueid,role_id) values (1,3);
-------------------------------------------------------------------------

create sequence refresh_tokens_id_seq START 1;

create table ib.refresh_tokens(
        id bigint not null default nextval('refresh_tokens_id_seq') PRIMARY KEY,
        uniqueid bigint,
        token varchar(500) not null,
        expiryDate TIMESTAMP WITH TIME ZONE not null
);

-------------------------------------------------------------------------

9. Implementieren Sie UserDetailsServiceImpl

Die UserDetailsServiceImpl-Klasse fungiert als Brücke zwischen der Datenbank Ihrer Anwendung und dem Authentifizierungsprozess von Spring Security. Es ruft mithilfe von CustomUserDetailsRepository Benutzerdetails aus der Datenbank ab, konvertiert das Benutzerobjekt in UserDetailsImpl (ein Spring Security-freundliches Format) und behandelt Fälle, in denen ein Benutzer nicht gefunden wird, indem es eine Ausnahme auslöst. Mit diesem Dienst kann Spring Security Benutzer authentifizieren und die Autorisierung basierend auf den Rollen und Berechtigungen des Benutzers verwalten.

package com.security.authmanager.model;

public enum ERole {
    ROLE_USER,
    ROLE_MODERATOR,
    ROLE_ADMIN
}

10. Filtern Sie die Anfrage

Die AuthTokenFilter-Klasse erweitert den OncePerRequestFilter von Spring und macht ihn zu einem Filter, der jede HTTP-Anfrage einmal in einer Anfragekette verarbeitet. Seine Hauptaufgabe besteht darin, ein JWT (JSON Web Token) aus der Anfrage zu extrahieren und zu validieren und die Authentifizierung des Benutzers im SecurityContext von Spring Security festzulegen, wenn das Token gültig ist.

package com.security.authmanager.model;

public class Role {
    private Integer id;
    private ERole name;

    public Role() {
    }

    public Role(ERole name) {
        this.name = name;
    }

    //generate getters and setters
}

Jedes Mal, wenn eine Anfrage gestellt wird:

  • Der Filter prüft, ob die Anfrage ein gültiges JWT enthält.
  • Wenn gültig, authentifiziert es den Benutzer durch Festlegen des UsernamePasswordAuthenticationToken im SecurityContext.
  • Wenn das JWT ungültig ist oder fehlt, wird keine Authentifizierung festgelegt und die Anfrage wird als nicht authentifizierte Anfrage fortgesetzt.

Dieser Filter stellt sicher, dass alle Anfragen mit gültigen JWT-Tokens automatisch authentifiziert werden, ohne dass der Benutzer nach der Anmeldung bei nachfolgenden Anfragen Anmeldeinformationen (wie Benutzername/Passwort) angeben muss.

11. Implementieren Sie RefreshTokenService

Die RefreshTokenService-Klasse stellt Dienste im Zusammenhang mit der Verwaltung von Aktualisierungstokens in einem tokenbasierten Authentifizierungssystem bereit. Aktualisierungstoken werden verwendet, um neue JWT-Tokens zu erhalten, nachdem das ursprüngliche JWT abgelaufen ist, ohne dass sich der Benutzer erneut authentifizieren muss.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

RefreshTokenService übernimmt die Erstellung, Validierung und Löschung von Aktualisierungstokens. Es verwendet Repositorys zum Speichern und Abrufen von Token und Benutzern aus der Datenbank.

Dieser Dienst ist ein wesentlicher Bestandteil eines Authentifizierungssystems, bei dem Aktualisierungstoken verwendet werden, um Benutzer angemeldet zu halten, ohne dass sie nach Ablauf des JWT erneut Anmeldeinformationen angeben müssen.

12. Implementieren Sie das JWT-Dienstprogramm

Die JwtUtils-Klasse ist eine Dienstprogrammklasse, die die Erstellung, Analyse und Validierung von JWT (JSON Web Token) für Authentifizierungszwecke in einer Spring Boot-Anwendung übernimmt. Es verwendet die jjwt-Bibliothek, um mit JWTs zu arbeiten.

spring.application.name= authmanager

server.port= 1001
servlet.context-path= /authmanager

database.username= postgres 
database.password= admin@123
database.driverClassName= org.postgresql.Driver
database.jdbcUrl= jdbc:postgresql://localhost:5432/postgres
database.maxActive= 5
database.minIdle= 5
database.poolName= Authmanager Postgres Datasource

app.jwtSecret= ###############ib-Spring###############
app.jwtExpirationMs= 3600000
app.jwtRefreshExpirationMs= 86400000

Die JwtUtils-Klasse ist für die Generierung, Analyse und Validierung von JWT-Tokens verantwortlich. Es signiert Token sicher mit einem geheimen Schlüssel (HMAC-SHA256) und stellt sicher, dass die Token nur von Parteien gelesen oder überprüft werden können, die über den richtigen geheimen Schlüssel verfügen.

Die Klasse extrahiert außerdem den Benutzernamen aus dem Token und prüft, ob das Token gültig ist, bevor sie dem Benutzer Zugriff gewährt. Dieses Dienstprogramm ist für die Aufrechterhaltung einer sicheren, tokenbasierten Authentifizierung in Ihrer Anwendung unerlässlich.

13. Authentifizierungsausnahme behandeln

Die AuthEntryPointJwt-Klasse implementiert die AuthenticationEntryPoint-Schnittstelle von Spring Security. Es verarbeitet, was passiert, wenn eine nicht autorisierte Anfrage gestellt wird, typischerweise wenn ein Benutzer versucht, ohne gültige Authentifizierung (z. B. kein JWT oder ein ungültiges JWT) auf eine geschützte Ressource zuzugreifen.

CREATE SCHEMA IB;
-------------------------------------------------------------------------

create sequence users_uniqueid_seq START 1;

create table ib.users(
        uniqueid bigint not null default nextval('users_uniqueid_seq') PRIMARY KEY,
        email varchar(75),
        password varchar(200),
        username varchar(20)
);

insert into ib.users(email,password,username) values ('admin@ib.com','a$VcdzH8Q.o4KEo6df.XesdOmXdXQwT5ugNQvu1Pl0390rmfOeA1bhS','admin');

#(password = 12345678)
-------------------------------------------------------------------------

create sequence roles_id_seq START 1;

create table ib.roles(
        id int not null default nextval('roles_id_seq') PRIMARY KEY,
        name varchar(20)
);

INSERT INTO ib.roles(name) VALUES('ROLE_USER');
INSERT INTO ib.roles(name) VALUES('ROLE_MODERATOR');
INSERT INTO ib.roles(name) VALUES('ROLE_ADMIN');

-------------------------------------------------------------------------

create table ib.user_roles(
        user_uniqueid bigint not null,
        role_id int not null,
        primary key(user_uniqueid,role_id)
);
insert into ib.user_roles (user_uniqueid,role_id) values (1,3);
-------------------------------------------------------------------------

create sequence refresh_tokens_id_seq START 1;

create table ib.refresh_tokens(
        id bigint not null default nextval('refresh_tokens_id_seq') PRIMARY KEY,
        uniqueid bigint,
        token varchar(500) not null,
        expiryDate TIMESTAMP WITH TIME ZONE not null
);

-------------------------------------------------------------------------

Die AuthEntryPointJwt-Klasse ist ein benutzerdefinierter Einstiegspunkt, der unbefugte Zugriffsversuche abfängt und eine strukturierte JSON-Antwort mit einem 401-Fehlercode, einer Fehlermeldung und Details zur Anfrage zurückgibt.
Es protokolliert den Fehler und bietet Clients eine klare, benutzerfreundliche Antwort, wenn die Authentifizierung fehlschlägt.

14. Erstellen Sie eine Nutzlastklasse für den Controller

Unten sind die Nutzlasten für unsere RestAPIs:

1. Anfragen:

- LoginRequest.java :

package com.security.authmanager.model;

public enum ERole {
    ROLE_USER,
    ROLE_MODERATOR,
    ROLE_ADMIN
}

- SignupRequest.java :

package com.security.authmanager.model;

public class Role {
    private Integer id;
    private ERole name;

    public Role() {
    }

    public Role(ERole name) {
        this.name = name;
    }

    //generate getters and setters
}

- TokenRefreshRequest.java :

package com.security.authmanager.model;

import java.util.HashSet;
import java.util.Set;

public class User {
    private Long id;
    private String username;
    private String email;
    private String password;

    private Set<Role> roles = new HashSet<>();

    public User() {
    }

    public User(String username, String email, String password) {
        this.username = username;
        this.email = email;
        this.password = password;
    }

    //generate getters and setters
}

2. Antworten:

- JwtResponse.java

package com.security.authmanager.model;

import java.util.HashSet;
import java.util.Set;

public class User {
    private Long id;
    private String username;
    private String email;
    private String password;

    private Set<Role> roles = new HashSet<>();

    public User() {
    }

    public User(String username, String email, String password) {
        this.username = username;
        this.email = email;
        this.password = password;
    }

    //generate getters and setters
}

- MessageResponse.java

package com.security.authmanager.repository;

import com.security.authmanager.common.QueryConstants;
import com.security.authmanager.model.ERole;
import com.security.authmanager.model.Role;
import com.security.authmanager.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

@Repository
public class CustomUserDetailsRepository {

    private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsRepository.class);
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public User fetchUserByUserName(String userName){
        try{
            return jdbcTemplate.query((conn) ->{
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.FETCH_USER);
                ps.setString(1, userName.toUpperCase());
                return ps;
            },rs->{
                User user = null;
                Set<Role> roles = new HashSet<>();
                while (rs.next()) {
                    if (user == null) {
                        user = new User();
                        user.setEmail(rs.getString("email"));
                        user.setId(rs.getLong("uniqueid"));
                        user.setPassword(rs.getString("password"));
                        user.setUsername(rs.getString("username"));
                    }
                    Role role = new Role();
                    role.setId(rs.getInt("id"));
                    role.setName(ERole.valueOf(rs.getString("name")));
                    roles.add(role);
                }
                if (user != null) {
                    user.setRoles(roles);
                }
                return user;
            });
        }catch(Exception e){
            logger.error("Exception in fetchUserByUserName()",e);
            throw new RuntimeException(e);
        }
    }

    public boolean existsByUsername(String userName) {
        try{
              return jdbcTemplate.query((conn) -> {
                 final PreparedStatement ps = conn.prepareStatement(QueryConstants.CHECK_USER_BY_USERNAME);
                 ps.setString(1, userName.toUpperCase());
                 return ps;
             }, (rs,rownum) -> rs.getInt("count")).get(0)>0;
        }catch(Exception e){
            logger.error("Exception in existsByUsername()",e);
            throw new RuntimeException(e);
        }
    }

    public boolean existsByEmail(String email) {
        try{
            return jdbcTemplate.query((conn) -> {
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.CHECK_USER_BY_EMAIL);
                ps.setString(1, email.toUpperCase());
                return ps;
            }, (rs,rownum) -> rs.getInt("count")).get(0)>0;
        }catch(Exception e){
            logger.error("Exception in existsByEmail()",e);
            throw new RuntimeException(e);
        }
    }

    public Role findRoleByName(ERole eRole) {
        try{
            return jdbcTemplate.query((conn) -> {
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.FETCH_ROLE_BY_NAME);
                ps.setString(1, String.valueOf(eRole));
                return ps;
            }, rs -> {
                Role role=null;
                while(rs.next()){
                    role = new Role();
                    role.setName(ERole.valueOf(rs.getString("name")));
                    role.setId(rs.getInt("id"));
                }
                return role;
            });
        }catch(Exception e){
            logger.error("Exception in findRoleByName()",e);
            throw new RuntimeException(e);
        }
    }

    public void createUser(User user) {
        try(Connection conn = Objects.requireNonNull(jdbcTemplate.getDataSource()).getConnection()){
            try (PreparedStatement userStatement = conn.prepareStatement(QueryConstants.INSERT_TO_USERS,Statement.RETURN_GENERATED_KEYS)) {
                userStatement.setString(1, user.getEmail().toUpperCase());
                userStatement.setString(2, user.getPassword());
                userStatement.setString(3, user.getUsername().toUpperCase());
                userStatement.executeUpdate();
                // Retrieve generated userId
                try (ResultSet generatedKeys = userStatement.getGeneratedKeys()) {
                    if (generatedKeys.next()) {
                        Long userId = generatedKeys.getLong(1); // Assuming userId is of type VARCHAR
                        logger.info("gen userid {}",userId.toString());
                        user.setId(userId);
                    }
                }
            }
            if (user.getRoles() != null && !user.getRoles().isEmpty()) {
                try (PreparedStatement userRoleStatement = conn.prepareStatement(QueryConstants.INSERT_TO_USER_ROLES)) {
                    for (Role role : user.getRoles()) {
                        userRoleStatement.setLong(1, user.getId());
                        userRoleStatement.setLong(2, role.getId());
                        userRoleStatement.executeUpdate();
                    }
                }
            }
        }catch(Exception e){
            logger.error("Exception in existsByEmail()",e);
            throw new RuntimeException(e);
        }
    }
}

- TokenRefreshResponse.java

package com.security.authmanager.repository;

import com.security.authmanager.common.QueryConstants;
import com.security.authmanager.model.ERole;
import com.security.authmanager.model.RefreshToken;
import com.security.authmanager.model.Role;
import com.security.authmanager.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.util.Optional;

@Repository
public class RefreshTokenRepository {
    private static final Logger logger = LoggerFactory.getLogger(RefreshTokenRepository.class);
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void deleteRefreshToken(RefreshToken refreshToken) {
        try{
            jdbcTemplate.update(QueryConstants.DELETE_REFRESH_TOKEN,(final PreparedStatement ps) ->{
                ps.setString(1,refreshToken.getToken());
            });
        }catch (Exception e){
            logger.error("Exception in deleteRefreshToken()",e);
            throw new RuntimeException(e);
        }
    }

    public int deleteRefreshTokenByUser(User user) {
        return 0;
    }

    public RefreshToken saveRefreshToken(RefreshToken refreshToken) {
        try{
            jdbcTemplate.update(QueryConstants.SAVE_REFRESH_TOKEN,(final PreparedStatement ps) ->{
                ps.setLong(1,refreshToken.getUser().getId());
                ps.setString(2,refreshToken.getToken());
                ps.setTimestamp(3, Timestamp.from(refreshToken.getExpiryDate()));
            });
        }catch (Exception e){
            logger.error("Exception in saveRefreshToken()",e);
            throw new RuntimeException(e);
        }
        return refreshToken;
    }

    public Optional<RefreshToken> findByToken(String token) {
        RefreshToken refreshToken = new RefreshToken();
        try{
            return Optional.ofNullable(jdbcTemplate.query((conn) -> {
                final PreparedStatement ps = conn.prepareStatement(QueryConstants.FIND_BY_TOKEN);
                ps.setString(1, token);
                return ps;
            }, rs -> {
                User user = new User();
                while (rs.next()) {
                    refreshToken.setId(rs.getLong("id"));
                    refreshToken.setToken(rs.getString("token"));
                    refreshToken.setExpiryDate(rs.getTimestamp("expiryDate").toInstant());
                    user.setId(rs.getLong("uniqueid"));
                    user.setEmail(rs.getString("email"));
                    user.setUsername(rs.getString("username"));
                    refreshToken.setUser(user);
                }
                return refreshToken;
            }));
        }catch(Exception e){
            logger.error("Exception in findByToken()",e);
            throw new RuntimeException(e);
        }
    }
}

15. Erstellen Sie Rest-APIs-Controller-Klassen

- AuthController.java

Die AuthController-Klasse ist ein Spring @RestController, der für die Handhabung authentifizierungsbezogener Endpunkte in der Anwendung verantwortlich ist. Es bietet Endpunkte für Benutzeranmeldung, Registrierung und Token-Aktualisierungsvorgänge.

Schlüsselfunktionen:

  • Benutzeranmeldung mit JWT und Aktualisierungstokengenerierung.
  • Token-Aktualisierung mit gültigen Aktualisierungstoken.
  • Benutzerregistrierung mit Validierung und Rollenzuweisung.
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

- TestController.java

Die TestController-Klasse ist ein Spring @RestController, der mehrere Endpunkte zum Testen der Zugriffskontrolle basierend auf Benutzerrollen bereitstellt. Es zeigt, wie man die rollenbasierte Autorisierung von Spring Security nutzt, um den Zugriff auf bestimmte Teile der Anwendung einzuschränken.

Schlüsselfunktionen:

  • Öffentlicher Zugriffsendpunkt, auf den jeder zugreifen kann.
  • Rollenspezifische Endpunkte, die den Zugriff basierend auf Benutzerrollen (BENUTZER, MODERATOR, ADMIN) einschränken.
spring.application.name= authmanager

server.port= 1001
servlet.context-path= /authmanager

database.username= postgres 
database.password= admin@123
database.driverClassName= org.postgresql.Driver
database.jdbcUrl= jdbc:postgresql://localhost:5432/postgres
database.maxActive= 5
database.minIdle= 5
database.poolName= Authmanager Postgres Datasource

app.jwtSecret= ###############ib-Spring###############
app.jwtExpirationMs= 3600000
app.jwtRefreshExpirationMs= 86400000

16. APIs testen

1. Registrieren Sie sich als Mod und Benutzer. (Anmelden)

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template

2. Melden Sie sich an, um ein Zugriffstoken zu erhalten.

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template

3. Holen Sie sich die Refresh-Token-API.

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template

4. Testen des Benutzerzugriffs durch Weitergabe des Zugriffstokens.

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template

5. Testen des Mod-Zugriffs durch Weitergabe des Zugriffstokens.

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template

6. Testen des Administratorzugriffs durch Weitergabe desselben Zugriffstokens.

Implementing Token-Based Authentication in Spring Boot Using Spring Security, JWT, and JDBC Template

Nicht autorisiert, da dieser Benutzer keinen Administratorzugriff hat (Benutzer hat nur Mod- und Benutzerrollen)

Viel Spaß beim Lernen! Wir sehen uns wieder.?

Das obige ist der detaillierte Inhalt vonImplementierung der tokenbasierten Authentifizierung in Spring Boot mithilfe von Spring Security, JWT und JDBC-Vorlage. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn