Github-Link für die Code-Referenz
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.
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.
In diesem Tutorial erfahren Sie, wie Sie:
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:
Technologische Anforderungen:
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>
Ö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
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 ); -------------------------------------------------------------------------
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 }
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:
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:
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>
Diese Klasse wird in Spring Security hauptsächlich verwendet, um den aktuell authentifizierten Benutzer darzustellen.
Wenn ein Benutzer versucht, sich anzumelden:
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 ); -------------------------------------------------------------------------
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 }
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:
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.
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.
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.
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.
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); } } }
- 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:
<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:
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
1. Registrieren Sie sich als Mod und Benutzer. (Anmelden)
2. Melden Sie sich an, um ein Zugriffstoken zu erhalten.
3. Holen Sie sich die Refresh-Token-API.
4. Testen des Benutzerzugriffs durch Weitergabe des Zugriffstokens.
5. Testen des Mod-Zugriffs durch Weitergabe des Zugriffstokens.
6. Testen des Administratorzugriffs durch Weitergabe desselben Zugriffstokens.
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!