20. Sicherung des Webdienstes für den Zugriff auf die Datenbank [dbproduitscategories]
20.1. Einrichtung der Arbeitsumgebung
Wir werden die Sicherheit des Webdienstes mit den folgenden Projekten einrichten:
![]() |
- Die Projekte [spring-security-*] befinden sich im Ordner [<exemples>\spring-database-generic\spring-security];
- Die Sicherheit wird für die Projekte SGBD und MySQL mit einer Ebene [DAO / JDBC] und anschließend einer Ebene [DAO / JPA / Hibernate] eingerichtet;
- Drücken Sie Alt-F5 und regenerieren Sie anschließend alle Maven-Projekte;
Wir müssen Benutzer in der Datenbank [dbproduitscategories] anlegen. Verwenden Sie dazu die Ausführungskonfiguration [spring-security-create-users-hibernate-eclipselink]:
![]() | ![]() |
Durch die Ausführung dieser Konfiguration werden die Tabellen [USERS, ROLES, USERS_ROLES] aus der Tabelle [dbproduitscategories] gefüllt:
![]() |
![]() |
Die erstellten IDs [login/passwd] lauten wie folgt: [admin/admin], [user/user], [guest/guest]. In der Datenbank sind die Passwörter verschlüsselt.
![]() |

Anschließend führen Sie die Ausführungskonfiguration mit dem Namen [spring-security-server-jpa-generic-hibernate-eclipselink] aus, die den gesicherten Webdienst startet (MySQL muss bereits gestartet sein):
![]() | ![]() |
Führen Sie anschließend die Ausführungskonfiguration mit dem Namen [spring-security-client-generic-JUnitTestDao] aus, die den sicheren Webdienst testet:
![]() | ![]() |
Der Test muss erfolgreich sein.
20.2. Das Eclipse-Projekt [spring-security-server-jdbc-generic]
Der gesicherte Webdienst wird durch das Projekt [spring-security-server-jdbc-generic] implementiert:
![]() |
Oben:
- Die Schicht [DAO1] ist die Schicht [DAO], die die Tabellen [PRODUITS] und [CATEGORIES] der Datenbank [dbproduitscategories] verwaltet. Sie wurde bereits geschrieben;
- die Schicht [DAO2] ist die Schicht [DAO], die die Tabellen [USERS], [ROLES] und [USERS_ROLES] der Datenbank [dbproduitscategories] verwaltet. Sie muss noch erstellt werden;
![]() |
20.2.1. Die Maven-Konfiguration
Das Projekt [spring-security-server-jdbc-generic] ist ein Maven-Projekt, das durch die folgende Datei [pom.xml] konfiguriert wird:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-security-server-jdbc-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-server-jdbc-generic</name>
<description>démo spring security</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Webserver / jSON -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-webjson-server-jdbc-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- Plugins -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- Zeilen 29–33: Hier wird die bestehende Konfiguration mit dem untersuchten Webservice-/JSON-/JDBC-Archiv übernommen;
- Zeilen 24–27: Die Abhängigkeit, die die Klassen von Spring Security bereitstellt;
Letztendlich weist das Projekt die folgenden Abhängigkeiten zu den anderen in Eclipse geladenen Projekten auf:
![]() |
20.2.2. Die Schicht [DAO2]
![]() |
Oben:
- Die Schicht [DAO1] ist die Schicht [DAO], die die Tabellen [PRODUITS] und [CATEGORIES] der Datenbank [dbproduitscategories] verwaltet. Sie wurde bereits geschrieben;
- die Schicht [DAO2] ist die Schicht [DAO], die die Tabellen [USERS], [ROLES] und [USERS_ROLES] der Datenbank [dbproduitscategories] verwaltet. Diese werden wir nun erstellen;
![]() |
Spring Security schreibt die Erstellung einer Klasse vor, die die folgende Schnittstelle [UsersDetail] implementiert:
![]() |
Diese Schnittstelle wird hier von der Klasse [AppUserDetails] implementiert:
package spring.security.dao;
import generic.jdbc.config.ConfigJdbc;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import spring.jdbc.entities.Role;
import spring.jdbc.entities.User;
import spring.jdbc.infrastructure.DaoException;
public class AppUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
// JdbcTemplate
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
// Eigenschaften
private User user;
private String simpleClassName = getClass().getSimpleName();
// Konstruktoren
public AppUserDetails() {
}
public AppUserDetails(User user, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.user = user;
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
// -------------------------Schnittstelle
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : getRoles(user.getId())) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
...
// private Methoden----------------
private List<Role> getRoles(Long id) {
try {
// Suche nach dem Benutzer anhand seiner ID
return namedParameterJdbcTemplate.query(ConfigJdbc.SELECT_ROLES_BYUSERID, Collections.singletonMap("id", id), new ShortRoleMapper());
} catch (Exception e) {
//e.printStackTrace();
throw new DaoException(167, e, simpleClassName);
}
}
}
// --------------------- Mapper
class ShortRoleMapper implements RowMapper<Role> {
@Override
public Role mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Role(rs.getLong("r_ID"), rs.getLong("r_VERSIONING"), rs.getString("r_NAME"));
}
}
- Zeile 22: Die Klasse [AppUserDetails] implementiert die Schnittstelle [UserDetails];
- Zeilen 29–30: Die Klasse kapselt einen Benutzer (Zeile 19) und das Repository, über das die Details dieses Benutzers abgerufen werden können (Zeile 20);
- Zeile 27: Der Zugriff auf die Datenbank erfolgt über JDBC mithilfe des Objekts [NamedParameterJdbcTemplate namedParameterJdbcTemplate], das im Projekt [spring-jdbc-generic-04] definiert ist. Es ist zu beachten, dass dieses Objekt nicht wie bisher üblich von Spring injiziert wird. Es wird dem Konstruktor in den Zeilen 36–39 übergeben. Warum? Weil die Klasse [AppUserDetails] keine Spring-Komponente ist (Fehlen der Annotation @Component) und daher keine Injektionen darin vorgenommen werden können;
- Zeilen 36–39: Der Konstruktor, der die Klasse mit einem Benutzer und dessen Repository instanziiert;
- Zeilen 42–49: Implementierung der Methode [getAuthorities] der Schnittstelle [UserDetails]. Sie muss eine Sammlung von Elementen vom Typ [GrantedAuthority] oder einem abgeleiteten Typ erstellen. Hier verwenden wir den abgeleiteten Typ [SimpleGrantedAuthority] (Zeile 46), der den Namen einer der Rollen des Benutzers aus Zeile 29 kapselt;
- Zeilen 45–47: Die Liste der Benutzerrollen aus Zeile 29 wird durchlaufen, um eine Liste von Elementen vom Typ [SimpleGrantedAuthority] zu erstellen;
- Zeile 45: Um die Rollen des Benutzers abzurufen, wird die private Methode [getRoles] aus Zeile 53 verwendet;
- Zeile 56: Führt den folgenden Befehl SQL [ConfigJdbc.SELECT_ROLES_BYUSERID] (definiert in [Configjdbc]) aus:
public static final String SELECT_ROLES_BYUSERID = "SELECT DISTINCT r.ID as r_ID, r.VERSIONING as r_VERSIONING, r.NAME as r_NAME FROM ROLES r, USERS u, USERS_ROLES ur WHERE u.ID=:id AND ur.USER_ID=u.ID AND ur.ROLE_ID=r.ID";
Diese Abfrage SQL führt eine Verknüpfung zwischen den drei Tabellen [USERS, ROLES, USERS_ROLES] durch, um die Rollen eines anhand seines Primärschlüssels identifizierten Benutzers zu ermitteln. Sie wird durch den Primärschlüssel [:id] des Benutzers parametrisiert, dessen Rollen gesucht werden.
- Zeile 56: Jede Ergebniszeile von [SELECT] wird durch die Klasse [ShortRowMapper] in den Zeilen 66–72 in eine Entität [Role] umgewandelt;
Kehren wir zum Code der Klasse [AppUserDetails] zurück:
package spring.security.dao;
...
public class AppUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
// JdbcTemplate
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
// Eigenschaften
private User user;
private String simpleClassName = getClass().getSimpleName();
// Konstruktoren
public AppUserDetails() {
}
public AppUserDetails(User user, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.user = user;
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
// -------------------------Schnittstelle
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : getRoles(user.getId())) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getLogin();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
// Getter und Setter
...
}
- Zeilen 35–37: implementieren die Methode [getPassword] der Schnittstelle [UserDetails]. Das Passwort des Benutzers aus Zeile 12 wird zurückgegeben;
- Zeilen 39–42: implementieren die Methode [getUserName] der Schnittstelle [UserDetails]. Der Benutzername des Benutzers aus Zeile 12 wird zurückgegeben;
- Zeilen 44–47: Das Benutzerkonto läuft nie ab;
- Zeilen 49–52: Das Benutzerkonto wird niemals gesperrt;
- Zeilen 54–57: Die Anmeldedaten des Benutzers verfallen nie;
- Zeilen 59–62: Das Benutzerkonto ist immer aktiv;
Spring Security schreibt außerdem vor, dass eine Klasse vorhanden sein muss, die die Schnittstelle [AppUserDetailsService] implementiert:
![]() |
Diese Schnittstelle wird von der folgenden Klasse „[AppUserDetailsService]“ implementiert:
package spring.security.dao;
import generic.jdbc.config.ConfigJdbc;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
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.Service;
import spring.jdbc.entities.User;
import spring.jdbc.infrastructure.DaoException;
@Service
public class AppUserDetailsService implements UserDetailsService {
// Injektionen
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
// lokal
private String simpleClassName = getClass().getSimpleName();
@Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
List<User> users;
try {
// Suche nach dem Benutzer anhand seines Logins
users = namedParameterJdbcTemplate.query(ConfigJdbc.SELECT_USER_BYLOGIN,
Collections.singletonMap("login", login), new ShortUserMapper());
} catch (Exception e) {
throw new DaoException(145, e, simpleClassName);
}
// Gefunden?
if (users.size() == 0) {
throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
}
// Benutzerdetails werden zurückgegeben
return new AppUserDetails(users.get(0), namedParameterJdbcTemplate);
}
}
// --------------------- Mapper
class ShortUserMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return new User(rs.getLong("u_ID"), rs.getLong("u_VERSIONING"), rs.getString("u_NAME"), rs.getString("u_LOGIN"),
rs.getString("u_PASSWORD"));
}
}
- Zeile 21: Die Klasse wird eine Spring-Komponente sein;
- Zeilen 25–26: Der Zugriff auf die Datenbank erfolgt über JDBC mithilfe des Objekts [NamedParameterJdbcTemplate namedParameterJdbcTemplate], das in den Beans des Projekts [spring-jdbc-generic-04] definiert ist;
- Zeilen 31–49: Implementierung der Methode [loadUserByUsername] der Schnittstelle [UserDetailsService] (Zeile 22). Der Parameter ist der Benutzername des Benutzers;
- Zeilen 36–37: Der Benutzer wird anhand seines Logins gesucht. Die Reihenfolge SQL [ConfigJdbc.SELECT_USER_BYLOGIN] lautet wie folgt:
public static final String SELECT_USER_BYLOGIN = "SELECT u.ID as u_ID, u.VERSIONING as u_VERSIONING, u.NAME as u_NAME,u.LOGIN as u_LOGIN,u.PASSWORD as u_PASSWORD FROM USERS u WHERE u.LOGIN= :login";
Jede von SELECT zurückgegebene Zeile wird durch die Klasse [ShortUserMapper] in den Zeilen 52–58 in eine Entität [User] umgewandelt.
- Zeilen 42–44: Wird es nicht gefunden, wird eine Ausnahme ausgelöst;
- Zeile 46: Ein Objekt vom Typ [AppUserDetails] wird erstellt und ausgegeben. Es ist tatsächlich vom Typ [UserDetails] (Zeile 32). Dem Konstruktor werden zwei Informationen übergeben:
- den gefundenen Benutzer;
- das Objekt [namedParameterJdbcTemplate], das es der Klasse [AppUserDetails] ermöglicht, die Datenbank abzufragen;
20.2.3. Die Schicht [web]
![]() |
Das Projekt [spring-security-server-jdbc-generic] hängt vom Projekt [spring-webjson-server-jdbc-generic] ab:
![]() |
Dieses Projekt implementiert die Schicht [web]. Diese muss nicht geändert werden.
20.2.4. Die Sicherheitskonfiguration des Projekts
![]() |
Das Projekt wird durch die folgende Klasse „[AppConfig]“ konfiguriert:
1 ![]() |
Wir sind bereits auf eine Konfigurationsklasse von Spring Security gestoßen (siehe Abschnitt 19.2.4):
package hello;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home").permitAll().anyRequest().authenticated();
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}
Wir werden denselben Ansatz verfolgen:
- Zeile 11: Definieren einer Klasse, die die Klasse [WebSecurityConfigurerAdapter] erweitert;
- Zeile 13: Definieren einer Methode [configure(HttpSecurity http)], die die Zugriffsrechte auf die verschiedenen URL des Webdienstes festlegt;
- Zeile 19: Definieren einer Methode [configure(AuthenticationManagerBuilder auth)], die die Benutzer und ihre Rollen festlegt;
Die Klasse [AppConfig] sieht wie folgt aus:
package spring.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import spring.security.dao.AppUserDetailsService;
import spring.webjson.server.config.WebConfig;
@Configuration
@EnableWebSecurity
@ComponentScan(basePackages = { "spring.security.dao", "spring.security.service" })
@Import({ spring.webjson.server.config.AppConfig.class, WebConfig.class })
public class AppConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AppUserDetailsService appUserDetailsService;
// Sicherung
private boolean activateSecurity = true;
@Override
protected void configure(AuthenticationManagerBuilder registry) throws Exception {
// Die Authentifizierung erfolgt über die Bean [appUserDetailsService]
// Das Passwort wird mit dem Hash-Algorithmus verschlüsselt BCrypt
registry.userDetailsService(appUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF
http.csrf().disable();
// Sichere Anwendung?
if (activateSecurity) {
// Das Passwort wird über den Header „Authorization: Basic xxxx“ übermittelt
http.httpBasic();
// Die Methode HTTP OPTIONS muss für alle autorisiert sein
http.authorizeRequests() //
.antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
// Nur die Rolle ADMIN darf die Anwendung nutzen
http.authorizeRequests() //
.antMatchers("/", "/**") // alle URL
.hasRole("ADMIN");
// Sitzung oder nicht?
//http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
}
- Zeile 17: Die Klasse ist eine Spring-Konfigurationsklasse;
- Zeile 18: Aktiviert die Spring-Security-Elemente;
- Zeile 26: Hier werden die Spring-Komponenten der Schicht „[DAO2]“ sowie die des Pakets „[spring.security.service]“ abgerufen, auf die wir später noch eingehen werden;
- Zeile 23: Hier werden die Beans aus dem Projekt [spring-webjson-server-jdbc-generic] importiert, das die Schicht [web] implementiert. Zu diesen Beans gehören auch diejenigen der Schicht [DAO1];
- Zeilen 22–23: Die Klasse [AppUserDetails], die den Benutzern Zugriff auf die Anwendung gewährt, wird injiziert;
- Zeile 26: Ein boolescher Wert, der die Webanwendung absichert (true) oder nicht (false);
- Zeilen 28–33: Die Methode [configure(HttpSecurity http)] definiert die Benutzer und ihre Rollen. Sie erhält als Parameter einen Typ [AuthenticationManagerBuilder]. Dieser Parameter wird um zwei Informationen ergänzt (Zeile 32):
- eine Referenz auf den Dienst [appUserDetailsService] aus Zeile 23, der Zugriff auf die registrierten Benutzer gewährt. Dabei ist zu beachten, dass nicht ersichtlich ist, ob diese in einer Datenbank gespeichert sind. Sie könnten sich also in einem Cache befinden, von einem Webdienst bereitgestellt werden usw.
- die für das Passwort verwendete Verschlüsselungsart. Wir haben den Algorithmus BCrypt verwendet;
- Zeilen 35–53: Die Methode [configure(HttpSecurity http)] definiert die Zugriffsrechte auf die URL des Webdienstes;
- Zeile 38: Wir haben im Einführungsprojekt gesehen, dass Spring Security standardmäßig ein CSRF-Token (Cross-Site-Request-Forgery) verwaltet, das der Benutzer, der sich authentifizieren möchte, an den Server zurücksenden muss. Hier ist dieser Mechanismus deaktiviert. Zusammen mit dem booleschen Wert (isSecured=false) ermöglicht dies die Nutzung der Webanwendung ohne Sicherheitsmaßnahmen;
- Zeile 42: Der Authentifizierungsmodus über den Header HTTP wird aktiviert. Der Client muss den folgenden Header HTTP senden:
wobei „code“ die Base64-Kodierung der Zeichenfolge „login:password“ ist. Beispielsweise lautet die Base64-Kodierung der Zeichenfolge „admin:admin“ „YWRtaW46YWRtaW4=“. Ein Benutzer mit dem Login „[admin]“ und dem Passwort „[admin]“ sendet also zur Authentifizierung den folgenden Header „HTTP“:
- Zeilen 47–49: Geben an, dass alle URL des Webdienstes für Benutzer mit der Rolle [ROLE_ADMIN] zugänglich sind. Das bedeutet, dass ein Benutzer ohne diese Rolle keinen Zugriff auf den Webdienst hat;
- Zeile 51: Im Modus [session] muss sich ein Benutzer, der sich einmal authentifiziert hat, bei seinen folgenden Zugriffen nicht erneut authentifizieren. Dies ist die Standardeinstellung von Spring Security. Zeile 51 deaktiviert diesen Modus. Ist er aktiv, muss sich der Benutzer bei jedem Zugriff authentifizieren. Ohne Sitzung ist die Reaktionsgeschwindigkeit des gesicherten Webdienstes geringer als mit Sitzung, daher wurde Zeile 51 auskommentiert;
20.2.5. Tests des gesicherten Webdienstes
Wir werden den Webdienst mit dem Chrome-Client [Advanced Rest Client] testen. Dazu müssen wir den Authentifizierungs-Header HTTP angeben:
wobei [code] der Base64-Code der Zeichenfolge [login:password] ist. Zur Generierung dieses Codes kann das folgende Programm aus dem Projekt [spring-security-create-users] verwendet werden:
![]() |
package spring.security.helpers;
import org.springframework.security.crypto.codec.Base64;
public class Base64Encoder {
public static void main(String[] args) {
// erwartet zwei Argumente: Login, Passwort
if (args.length != 2) {
System.out.println("Syntaxe : login password");
System.exit(0);
}
// Die beiden Argumente werden abgerufen
String chaîne = String.format("%s:%s", args[0], args[1]);
// Die Zeichenkette wird kodiert
byte[] data = Base64.encode(chaîne.getBytes());
// die Base64-Kodierung wird angezeigt
System.out.println(new String(data));
}
}
Wenn wir dieses Programm mit den beiden Argumenten [admin admin] ausführen:
![]() |
erhalten wir folgendes Ergebnis:
Nun sind wir bereit für die Tests:
- Das Programm SGBD MySQL muss gestartet werden;
- wir füllen die Tabellen [PRODUITS] und [CATEGORIES] mit der Ausführungskonfiguration namens [spring-jdbc-generic-04-fillDataBase]:
![]() |
- Falls dies noch nicht geschehen ist, füllen wir die Tabellen [USERS, ROLES, USERS_ROLES] mit der Ausführungskonfiguration namens [spring-security-create-users-hibernate-eclipselink]:
![]() |
- Wir starten den sicheren Webdienst mit der Ausführungskonfiguration namens [spring-security-server-jdbc-generic]:
![]() |
Anschließend fordern wir mit dem Chrome-Client [Advanced Rest Client] die Langfassung aller Kategorien an:
![]() |
- In [1] fordern wir die URL der ausführlichen Kategorien an;
- in [2] mit der Methode GET;
- in [3] geben wir den Header HTTP für die Authentifizierung an. Der Code [YWRtaW46YWRtaW4=] ist die Base64-Kodierung der Zeichenfolge [admin:admin];
- Bei [4] senden wir den Befehl HTTP;
Die Antwort des Servers lautet wie folgt:
![]() |
- in [1], der Authentifizierungsheader HTTP;
- in [2] sendet der Server eine Antwort mit dem Header jSON zurück;
Man erhält tatsächlich die Liste der Kategorien:
![]() |
Versuchen wir nun eine Anfrage HTTP mit einem falschen Authentifizierungsheader. Die Antwort lautet dann wie folgt:
![]() |
- in [1]: der Authentifizierungsheader HTTP;
Wir erhalten folgende Antwort:
![]() |
- zu [2]: die Antwort des Webdienstes;
Versuchen wir es nun mit dem Benutzer „user / user“. Er existiert, hat aber keinen Zugriff auf den Webdienst. Wenn wir das Base64-Kodierungsprogramm mit den beiden Argumenten [user user] ausführen:
![]() |
erhalten wir folgendes Ergebnis:
![]() |
- in [1]: der fehlerhafte Authentifizierungsheader HTTP;
![]() |
- in [2]: die Antwort des Webdienstes. Sie unterscheidet sich von der vorherigen, die [401 Unauthorized] lautete. Diesmal hat sich der Benutzer zwar korrekt authentifiziert, verfügt jedoch nicht über ausreichende Rechte, um auf URL zuzugreifen;
Ein sicherer Webdienst ist nun betriebsbereit.
20.2.6. Eine Authentifizierungs-URL
![]() |
Wir werden einen URL erstellen, mit dem wir feststellen können, ob ein Benutzer zum Zugriff auf den Webdienst berechtigt ist oder nicht. Dazu erstellen wir den folgenden neuen Controller MVC [AuthenticateController]:
package spring.security.service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import spring.webjson.server.service.Response;
@RestController
public class AuthenticateController {
@RequestMapping(value = "/authenticate", method = RequestMethod.GET)
public Response<Void> authenticate() {
return new Response<Void>(0, null, null);
}
}
- Zeile 9: Die Klasse [AuthenticateController] ist ein Spring-Controller. Als solcher stellt sie URL bereit. Die Annotation [@RestController] gibt an, dass die Methoden, die diese URL verarbeiten, ihre Antwort selbst an den Client zurückgeben;
- Zeile 11: stellt das URL [/authenticate] bereit;
- Zeilen 12–14: Die Methode gibt lediglich ein leeres [Response]-Objekt zurück, dessen [status]-Wert jedoch 0 ist, was anzeigt, dass kein Fehler aufgetreten ist;
Wozu dient dieses URL? Wenn wir einen Benutzer einfach nur authentifizieren wollen, werden wir danach fragen. Wir haben gesehen, dass die Sicherheitsschicht eine Ausnahme zurückgibt, wenn sie diesen Benutzer nicht akzeptiert. Hier ein Beispiel;
Mit dem Benutzer [admin:admin]:
![]() | ![]() |
Wir erhalten eine leere Antwort, aber keine Ausnahme.
Mit dem Benutzer [user:user]:
![]() | ![]() |
Es ist eine Ausnahme aufgetreten.
20.2.7. Fazit
Das Hinzufügen der für Spring Security erforderlichen Klassen konnte ohne Änderungen am ursprünglichen Web-/JSON-Projekt erfolgen. Dieser sehr günstige Fall ergibt sich daraus, dass die drei in die Datenbank hinzugefügten Tabellen unabhängig von den bestehenden Tabellen sind. Man hätte sie sogar in einer separaten Datenbank ablegen können. In anderen Fällen können die hinzugefügten Tabellen Beziehungen zu den bestehenden Tabellen aufweisen. Der Code der bestehenden Schicht [DAO] muss dann überarbeitet werden.
20.3. Ein Client, der für den gesicherten Webdienst / jSON programmiert wurde
Wir haben bereits einen Client für den ungesicherten Webdienst / jSON geschrieben:
![]() |
Nun erstellen wir einen Client für den gesicherten Webdienst:
![]() |
![]() |
20.3.1. Die Schicht [Client HTTP]
![]() |
![]() | ![]() |
Die Klasse [Client] stellt die Kommunikation HTTP mit dem gesicherten Webserver / jSON sicher. Wie wir gerade gesehen haben, muss der Client bei dieser Kommunikation HTTP nun einen Authentifizierungsheader senden, zum Beispiel:
Die Schnittstelle [IClient] sieht nun wie folgt aus:
package spring.webjson.client.dao;
import org.springframework.http.HttpMethod;
import spring.webjson.client.entities.Credentials;
public interface IClient {
public <T1, T2> T1 getResponse(Credentials credentials,String url, HttpMethod method, int errStatus, T2 body);
}
- Zeile 8: Der erste Parameter der Methode [getResponse] ist nun ein Objekt vom Typ [Credentials], das die Anmeldedaten eines Benutzers enthält:
package spring.security.client.entities;
public class Credentials {
// Eigenschaften
private String login;
private String password;
// Konstruktor
public Credentials() {
}
public Credentials(String login, String password) {
this.login = login;
this.password = password;
}
// Getter und Setter
...
}
Die Klasse [Client], die die Schnittstelle [IClient] implementiert, ändert sich wie folgt:
package spring.security.client.dao;
...
@Component
public class Client implements IClient {
// Injektionen
@Autowired
protected RestTemplate restTemplate;
@Autowired
protected String urlServiceWebJson;
// lokal
private String simpleClassName = getClass().getSimpleName();
private String getBase64(Credentials credentials) {
// Benutzername und Passwort werden in Base64 kodiert – erfordert Java 8
String chaîne = String.format("%s:%s", credentials.getLogin(), credentials.getPassword());
return String.format("Basic %s", new String(Base64.getEncoder().encode(chaîne.getBytes())));
}
// generische Anfrage
@Override
public <T1, T2> T1 getResponse(Credentials credentials, String url, HttpMethod method, int errStatus, T2 body) {
// die Antwort des Servers
ResponseEntity<Response<T1>> response;
try {
// Die Anfrage wird vorbereitet
RequestEntity<?> request = null;
if (method == HttpMethod.GET) {
HeadersBuilder<?> headersBuilder = RequestEntity.get(new URI(String.format("%s%s", urlServiceWebJson, url))) .accept(MediaType.APPLICATION_JSON);
if (credentials != null) {
headersBuilder = headersBuilder.header("Authorization", getBase64(credentials));
}
request = headersBuilder.build();
}
if (method == HttpMethod.POST) {
BodyBuilder bodyBuilder = RequestEntity.post(new URI(String.format("%s%s", urlServiceWebJson, url))).header("Content-Type", "application/json").accept(MediaType.APPLICATION_JSON);
if (credentials != null) {
bodyBuilder = bodyBuilder.header("Authorization", getBase64(credentials));
}
request = bodyBuilder.body(body);
}
// Die Anfrage wird ausgeführt
response = restTemplate.exchange(request, new ParameterizedTypeReference<Response<T1>>() {
});
} catch (Exception e) {
// Die Ausnahme wird gekapselt
throw new DaoException(errStatus, e, simpleClassName);
}
...
}
...
}
- Zeilen 33–35, 40–42: Ist der Benutzer [credentials] nicht null, wird der Authentifizierungsheader hinzugefügt. Die Verschlüsselung Base64 des Benutzers und seines Passworts erfolgt durch die Methode [getBase64] in den Zeilen 17–21. Es ist zu beachten, dass diese Methode eine Klasse [Base64] verwendet, die zu JDK 1.8 gehört. Unser Client HTTP kann mit einem ungesicherten Webdienst arbeiten. Man muss ihm lediglich ein [credentials] übergeben, das gleich null ist;
- abgesehen von den vorangegangenen Zeilen bleibt der Code unverändert;
20.3.2. Die Schicht [DAO]
![]() |
20.3.2.1. Die Schnittstelle [IDao]
![]() |
Alle Methoden der Schnittstelle [IDao] des Projekts [spring-webjson-client-generic] erhalten einen zusätzlichen Parameter [Credentials credentials]:
package spring.security.client.dao;
import java.util.List;
import spring.security.client.entities.AbstractCoreEntity;
import spring.security.client.entities.Credentials;
public interface IDao<T extends AbstractCoreEntity> extends IAuthenticate {
// Liste aller T-Entitäten
public List<T> getAllShortEntities(Credentials credentials);
public List<T> getAllLongEntities(Credentials credentials);
// bestimmter Entitäten – Kurzversion
public List<T> getShortEntitiesById(Credentials credentials, Iterable<Long> ids);
public List<T> getShortEntitiesById(Credentials credentials, Long... ids);
public List<T> getShortEntitiesByName(Credentials credentials, Iterable<String> names);
public List<T> getShortEntitiesByName(Credentials credentials, String... names);
// bestimmter Entitäten – Langfassung
public List<T> getLongEntitiesById(Credentials credentials, Iterable<Long> ids);
public List<T> getLongEntitiesById(Credentials credentials, Long... ids);
public List<T> getLongEntitiesByName(Credentials credentials, Iterable<String> names);
public List<T> getLongEntitiesByName(Credentials credentials, String... names);
// Aktualisierung mehrerer Entitäten
public List<T> saveEntities(Credentials credentials, Iterable<T> entities);
public List<T> saveEntities(Credentials credentials, @SuppressWarnings("unchecked") T... entities);
// Löschen aller Entitäten
public void deleteAllEntities(Credentials credentials);
// Löschen mehrerer Entitäten
public void deleteEntitiesById(Credentials credentials, Iterable<Long> ids);
public void deleteEntitiesById(Credentials credentials, Long... ids);
public void deleteEntitiesByName(Credentials credentials, Iterable<String> names);
public void deleteEntitiesByName(Credentials credentials, String... names);
public void deleteEntitiesByEntity(Credentials credentials, Iterable<T> entities);
public void deleteEntitiesByEntity(Credentials credentials, @SuppressWarnings("unchecked") T... entities);
}
- Zeile 8: Die Schnittstelle [IDao] erweitert die folgende Schnittstelle [IAuthenticate]:
package spring.security.client.dao;
import spring.security.client.entities.Credentials;
public interface IAuthenticate {
// Authentifizierung
public void authenticate(Credentials credentials);
}
Die Schnittstelle [IAuthenticate] verfügt nur über die einzige Methode [authenticate]. Diese gibt nichts zurück (void), wenn der Benutzer [Credentials credentials] vom gesicherten Webdienst akzeptiert wird, andernfalls wird eine Ausnahme ausgelöst.
20.3.2.2. Die Klasse [AbstractDao]
![]() |
Zur Erinnerung: Die Klasse [AbstractDao] ist die übergeordnete Klasse der Klassen [DaoCategorie], die die URL der Kategorien verwalten, und der Klasse [DaoProduit], die die URL der Produkte verwaltet. Alle Methoden der Klasse [AbstractDao] des Projekts [spring-webjson-client-generic] erhalten einen zusätzlichen Parameter [Credentials credentials], den sie an die untergeordnete Klasse weitergeben. Hier ein Beispiel:
@Override
public List<T1> getShortEntitiesById(Credentials credentials, Iterable<Long> ids) {
// Gültigkeit des Arguments
List<T1> entities = checkNullOrEmptyArgument(true, ids);
if (entities != null) {
return entities;
}
// Ergebnis
return getShortEntitiesById(credentials, Lists.newArrayList(ids));
}
- Die Methode [getShortEntitiesById] erhält den Parameter [Credentials credentials] (Zeile 2), den sie (Zeile 9) an die Methode [getShortEntitiesById] der untergeordneten Klasse weiterleitet;
Die Klasse [AbstractDao] hat das folgende Grundgerüst:
package spring.security.client.dao;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import spring.security.client.entities.AbstractCoreEntity;
import spring.security.client.entities.Credentials;
import spring.security.client.infrastructure.MyIllegalArgumentException;
import com.google.common.collect.Lists;
public abstract class AbstractDao<T1 extends AbstractCoreEntity> implements IDao<T1> {
@Autowired
private IAuthenticate authenticate;
...
}
- Zeile 14: Die Klasse implementiert die von uns beschriebene Schnittstelle [IDao];
- Zeilen 16–17: Eine Instanz der Schnittstelle [IAuthenticate] wird injiziert. Diese wird von der folgenden Klasse [Authenticate] implementiert:
package spring.security.client.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import spring.security.client.entities.Credentials;
@Component
public class Authenticate implements IAuthenticate{
@Autowired
protected IClient client;
// Überprüfung [credentials,mdp]
public void authenticate(Credentials credentials) {
client.<Void, Void> getResponse(credentials, "/authenticate", HttpMethod.GET, 111, (Void) null);
}
}
- Zeile 9: Die Klasse [Authenticate] ist eine Spring-Komponente;
- Zeile 10: Diese implementiert die Schnittstelle [IAuthenticate];
- Zeilen 11–12: Injektion des Clients HTTP, der die Kommunikation mit dem gesicherten Webdienst ermöglicht;
- Zeilen 15–17: Implementierung der Methode [authenticate] der Schnittstelle;
- Zeile 16: Ein Befehl HTTP GET wird an URL [/authenticate] gesendet. Die Verwendung dieser URL wurde in Abschnitt 20.2.6 erläutert. Das Prinzip besteht darin, dass der Aufruf mit einer Ausnahme endet, wenn der Benutzer [credentials] entweder unbekannt ist oder nicht über ausreichende Rechte verfügt;
Die Klasse [AbstractDao] implementiert die Methode [authenticate] der Schnittstelle [IDao] wie folgt:
@Autowired
private IAuthenticate authenticate;
@Override
public void authenticate(Credentials credentials) {
authenticate.authenticate(credentials);
}
- Zeile 7: Die Aufgabe wird an die Methode [authenticate] der Klasse [Authenticate] delegiert. Es kommt also zu einer Ausnahme, wenn der Benutzer [Credentials credentials] vom gesicherten Webdienst nicht akzeptiert wird;
20.3.2.3. Die Klassen [DaoCategorie, DaoProduit]
![]() |
Die Klassen [DaoCategorie, DaoProduit] entsprechen denen des Projekts [spring-webjson-server-generic] mit dem zusätzlichen Parameter [Credentials credentials]. Hier ein Beispiel:
@Component
public class DaoCategorie extends AbstractDao<Categorie> {
// Eingaben
@Autowired
protected ApplicationContext context;
@Autowired
protected IClient client;
@Override
public List<Categorie> getAllShortEntities(Credentials credentials) {
try {
// Filter jSON
ObjectMapper mapper = context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// Alle Kategorien anzeigen
Object map = client.<List<Categorie>, Void> getResponse(credentials, "/getAllShortCategories", HttpMethod.GET,
202, null);
// die Liste der Kategorien List<Kategorie>
return mapper.readValue(mapper.writeValueAsString(map), new TypeReference<List<Categorie>>() {
});
} catch (DaoException e1) {
throw e1;
} catch (Exception e2) {
throw new DaoException(221, e2, simpleClassName);
}
}
....
20.3.3. Die Spring-Konfiguration
![]() |
Die Klasse [AppConfig] konfiguriert die Spring-Umgebung des Projekts. Sie ist identisch mit der Klasse im Projekt [spring-webjson-client-generic], bis auf ein Detail:
@Configuration
@ComponentScan({ "spring.security.client.dao" })
public class AppConfig {
- Zeile 2: Hier muss das Paket der neuen Schicht [DAO] angegeben werden;
20.3.4. Tests der Schicht [DAO]
![]() |
![]() |
20.3.4.1. Der Test [JUnitTestCredentials]
Der Test [JUnitTestCredentials] verwendet die Methode [IDao.authenticate], um die Gültigkeit bestimmter Benutzer zu überprüfen:
package client.tests.junit;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import spring.security.client.config.AppConfig;
import spring.security.client.dao.IAuthenticate;
import spring.security.client.entities.Credentials;
import spring.security.client.infrastructure.DaoException;
@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTestCredentials {
// Ebene [DAO]
@Autowired
private IAuthenticate authenticate;
// Benutzer
static private Credentials admin;
static private Credentials user;
static private Credentials unknown;
@BeforeClass
public static void init() {
admin = new Credentials("admin", "admin");
user = new Credentials("user", "user");
unknown = new Credentials("x", "y");
}
@Test()
public void checkUserUser() {
DaoException se = null;
try {
authenticate.authenticate(user);
} catch (DaoException e) {
se = e;
System.out.println("checkUserUser: " + e);
}
Assert.assertNotNull(se);
Assert.assertEquals("403 Forbidden", se.getExceptions().get(0).getErrorMessage());
}
@Test()
public void checkUserUnknown() {
DaoException se = null;
try {
authenticate.authenticate(unknown);
} catch (DaoException e) {
se = e;
System.out.println("checkUserUnknown : " + e);
}
Assert.assertNotNull(se);
Assert.assertEquals("401 Unauthorized", se.getExceptions().get(0).getErrorMessage());
}
@Test()
public void checkUserAdmin() {
DaoException se = null;
try {
authenticate.authenticate(admin);
} catch (DaoException e) {
se = e;
System.out.println("checkUserAdmin : " + e);
}
Assert.assertNull(se);
}
}
- Bei der Initialisierung der Testklasse (Zeilen 29–34) werden drei Benutzer angelegt:
- Der Benutzer [admin] hat Zugriff auf die URL des Webdienstes. Dies wird in den Zeilen 63–72 getestet;
- Der Benutzer [user] existiert, ist jedoch nicht berechtigt, die URL des Webdienstes zu nutzen. Er wird in den Zeilen 37–47 getestet;
- Der Benutzer [unknown] existiert nicht. Dies wird in den Zeilen 50–60 geprüft;
Der gesicherte Webdienst wird mit der Ausführungskonfiguration namens [spring-security-server-jdbc-generic] [1] gestartet:
![]() |
Anschließend wird der Test JUnit [JUnitTestCredentials] mit der Ausführungskonfiguration [spring-security-client-generic-JUnitTestCredentials] [2] gestartet. Die folgenden Konsolenergebnisse werden ausgegeben:
und der Test ist erfolgreich:
![]() |
20.3.4.2. Der Test [JUnitTestDao]
Der Test [JUnitTestDao] ist identisch mit dem im ungesicherten Projekt [spring-webjson-client-generic], mit dem einzigen Unterschied,dass nun die getesteten Methoden der Schicht [DAO] alle den Benutzer [admin / admin] als ersten Parameter haben:
@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTestDao {
// Spring-Kontext
@Autowired
private ApplicationContext context;
// Schicht [DAO]
@Autowired
private IDao<Produit> daoProduit;
@Autowired
private IDao<Categorie> daoCategorie;
....
// Benutzer
static private Credentials admin;
@BeforeClass
public static void init() {
admin = new Credentials("admin", "admin");
}
@Before
public void clean() {
// Die Datenbank wird vor jedem Test bereinigt
log("Vidage de la base de données", 1);
// Die Tabelle [CATEGORIES] wird geleert und in der Folge auch die Tabelle [PRODUITS]
daoCategorie.deleteAllEntities(admin);
// Die Wörterbücher werden geleert
for (Long id : mapCategories.keySet()) {
mapCategories.remove(id);
}
for (Long id : mapProduits.keySet()) {
mapProduits.remove(id);
}
}
private List<Categorie> fill(int nbCategories, int nbProduits) {
// Die Tabellen werden gefüllt
...
// Hinzufügen der Kategorie – in einer kaskadierenden Aktion werden auch die Produkte
// eingefügt – das Ergebnis wird gleichzeitig ausgegeben
return daoCategorie.saveEntities(admin, categories);
}
private Object[] showDataBase() throws BeansException, JsonProcessingException {
// Liste der Kategorien
log("Liste des catégories", 2);
List<Categorie> categories = daoCategorie.getAllShortEntities(admin);
affiche(categories, context.getBean("jsonMapperShortCategorie", ObjectMapper.class));
// Liste der Produkte
log("Liste des produits", 2);
List<Produit> produits = daoProduit.getAllShortEntities(admin);
affiche(produits, context.getBean("jsonMapperShortProduit", ObjectMapper.class));
// Ergebnis
return new Object[] { categories, produits };
}
...
Alle Vorgänge werden mit dem Benutzer [admin / admin] durchgeführt, der als Einziger Zugriffsrechte auf den gesicherten Webdienst hat.
Der Test wird mit der Ausführungskonfiguration namens [spring-security-client-generic-JUnitTestDao] gestartet:
![]() |
Der Test wird erfolgreich durchgeführt, es ist jedoch festzustellen, dass er langsamer ist als bei dem ungesicherten Webdienst. Die Absicherung einer Anwendung erhöht deren Antwortzeiten merklich. Ein wichtiger Faktor für die Leistung des gesicherten Webdienstes ist zu beachten: In der Klasse „[AppConfig]“, die ihn konfiguriert, haben wir Folgendes geschrieben:
@Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF
http.csrf().disable();
// Sichere Anwendung?
if (activateSecurity) {
// Das Passwort wird über den Header „Authorization: Basic xxxx“ übermittelt
http.httpBasic();
// Die Methode HTTP OPTIONS muss für alle autorisiert sein
http.authorizeRequests() //
.antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
// Nur die Rolle ADMIN darf die Anwendung nutzen
http.authorizeRequests() //
.antMatchers("/", "/**") // alle URL
.hasRole("ADMIN");
// keine Sitzung
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
Zeile 17 hat ihren Preis. Sie legt fest, ob sich der Benutzer bei jedem Zugriff authentifizieren muss oder nicht. Wenn man sie auskommentiert, ist die Dauer des Tests JUnit deutlich kürzer, da sich der Benutzer [admin] nur beim ersten Test authentifiziert und nicht bei den folgenden (auch wenn der Authentifizierungsheader HTTP vom Client gesendet wird, überprüft der Server das Passwort des Benutzers nicht erneut).
20.4. Das Eclipse-Projekt [spring-security-server-jpa-generic]
Der gesicherte Webdienst wird nun durch das Projekt [spring-security-server-jpa-generic] implementiert, das auf dem Projekt [spring-jpa-generic] basiert, welches den Zugriff auf die Datenbank mit Spring Data JPA verwaltet:
![]() |
Oben:
- Die Schicht [DAO1] ist die Schicht [DAO], die die Tabellen [PRODUITS] und [CATEGORIES] der Datenbank [dbproduitscategories] verwaltet. Sie wurde bereits geschrieben;
- die Schicht [DAO2] ist die Schicht [DAO], die die Tabellen [USERS], [ROLES] und [USERS_ROLES] der Datenbank [dbproduitscategories] verwaltet. Sie muss noch erstellt werden;
Das Projekt [spring-security-server-jpa-generic] wird zunächst durch Kopieren des zuvor untersuchten Projekts [spring-security-server-jdbc-generic] erstellt. Tatsächlich ändern sich die Schichten [web] und [security] nicht, da:
- die Schicht [DAO1 / Repositories / JPA] (bereits geschrieben) dieselbe Schnittstelle wie die Schicht [DAO1 / JDBC] hat;
- die Schicht [DAO2 / Repositories / JPA] (die noch geschrieben werden muss) wird dieselbe Schnittstelle wie die Schicht [DAO2 / JDBC] haben;
Das Projekt [spring-security-server-jpa-generic] sieht wie folgt aus:
![]() |
- Das Paket [spring.security.repositories] implementiert die Schicht [repositories];
- Das Paket [spring.security.dao] implementiert die Schicht [dao2];
20.4.1. Das Maven-Projekt
Das Projekt ist ein Maven-Projekt, das durch die folgende Datei [pom.xml] konfiguriert wird:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-security-server-jpa-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-server-jpa-generic</name>
<description>démo spring security</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Webserver / jSON -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-webjson-server-jpa-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- Plugins -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- Zeilen 24–27: die Abhängigkeit zur Schicht [security] des Projekts;
- Zeilen 29–33: die Abhängigkeit für die Schicht [web] des Projekts. Das Projekt [spring-webjson-server-jpa-generic] implementiert die Schicht [web] vollständig. Diese muss weder geschrieben noch geändert werden;
Letztendlich ergeben sich folgende Abhängigkeiten:
![]() |
20.4.2. Die Spring-Konfiguration
![]() |
Die Konfigurationsdatei [AppConfig] aus dem vorherigen Projekt [spring-security-server-jdbc-generic] ist geeignet. Es muss lediglich eine zusätzliche Konfiguration hinzugefügt werden:
@Configuration
@EnableWebSecurity
@EnableJpaRepositories(basePackages = { "spring.security.repositories" })
@ComponentScan(basePackages = { "spring.security.dao", "spring.security.service" })
@Import({ spring.webjson.server.config.AppConfig.class })
public class AppConfig extends WebSecurityConfigurerAdapter {
- Zeile 3: Hier wird das Paket deklariert, das die Schicht [repositories] implementiert;
- Zeile 4: Die Pakete, die die Spring-Beans enthalten, tragen im neuen Projekt denselben Namen;
- Zeile 5: Im vorherigen Projekt befand sich die Klasse [spring.webjson.server.config.AppConfig] in der Abhängigkeit [spring-webjson-server-jdbc-generic]. Hier befindet sie sich in der Abhängigkeit [spring-webjson-server-jpa-generic];
20.4.3. Die Schicht JPA
![]() |
Die Entitäten JPA, die von der Ebene [JPA] verwaltet werden, befinden sich im Projekt [mysql-config-jpa-hibernate] [2], das eine Abhängigkeit des Projekts QZXW2HTMLP005266Z istQX:
![]() |
Die Klasse [User] ist das Abbild der Tabelle [USERS]:

- ID: Primärschlüssel;
- VERSION: Versionsspalte der Zeile;
- IDENTITY: eine beschreibende Kennung des Benutzers;
- LOGIN: der Benutzername des Benutzers;
- PASSWORD: sein Passwort;
package generic.jpa.entities.dbproduitscategories;
import generic.jdbc.config.ConfigJdbc;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = ConfigJdbc.TAB_USERS)
public class User implements AbstractCoreEntity {
// Eigenschaften
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = ConfigJdbc.TAB_JPA_ID)
protected Long id;
@Version
@Column(name = ConfigJdbc.TAB_JPA_VERSIONING)
protected Long version;
@Transient
protected EntityType entityType=EntityType.POJO;
// Eigenschaften
@Column(name = ConfigJdbc.TAB_USERS_NAME, length = 30, nullable = false)
private String name;
@Column(name = ConfigJdbc.TAB_USERS_LOGIN, length = 30, unique = true, nullable = false)
private String login;
@Column(name = ConfigJdbc.TAB_USERS_PASSWORD, length = 60, nullable = false)
private String password;
// die zugehörigen UserRole
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
@JsonIgnore
private List<UserRole> userRoles;
// Konstruktoren
public User() {
}
public User(Long id, Long version, String identity, String login, String password) {
this.id = id;
this.version = version;
this.name = identity;
this.login = login;
this.password = password;
}
// ------------------------------------------------------------
// Neudefinition von [equals] und [hashcode]
...
// Getter und Setter
...
}
- Zeile 23: Die Klasse implementiert die Schnittstelle [AbstractCoreEntity], die bereits für andere Entitäten verwendet wird;
- Zeilen 34–35: Der Typ der Entität. Diese Eigenschaft wird nicht in der Datenbank gespeichert ([@Transient]);
- Zeilen 38–43: Die drei grundlegenden Eigenschaften eines Benutzers (name, login, password);
- Zeilen 46–48: die Liste der Rollen des Benutzers. Er kann mehrere davon haben. Ebenso werden wir sehen, dass einer Rolle mehrere Benutzer zugeordnet sein können. Im Sinne des Begriffs im JPA besteht also eine Beziehung [ManyToMany] zwischen den Entitäten [User] und [Role]:
- Ein Benutzer kann mehrere Rollen referenzieren;
- eine Rolle kann auf mehrere Benutzer verweisen;
Diese Beziehung [ManyToMany] wird in der Datenbank durch die Verknüpfungstabelle [USERS_ROLES] implementiert. Wenn ein Benutzer U eine Beziehung zu einer Rolle R hat, wird diese Beziehung in der Tabelle [USERS_ROLES] gespeichert, indem das Primärschlüsselpaar der Entitäten (U, R) erfasst wird. In der Tabelle JPA kann die Beziehung [ManyToMany], die die Entitäten [User] und [Role] verbindet, in zwei Beziehungen [ManyToOne, OneToMany] aufgeteilt werden:
- (Fortsetzung)
- eine Beziehung [ManyToOne] von der Entität [User] zur Entität [UserRole];
- eine Beziehung [OneToMany] von der Entität [UserRole] zur Entität [UserRole];
Ebenso lässt sich die Beziehung [ManyToMany], die die Entitäten [Role] und [User] verbindet, in zwei Beziehungen [ManyToOne, OneToMany] aufteilen:
- (Fortsetzung)
- eine Beziehung [ManyToOne] von der Entität [Role] zur Entität [UserRole];
- eine Beziehung [OneToMany] von der Entität [UserRole] zur Entität [User];
- Beziehung 48: Die Tatsache, dass ein Benutzer mehrere Rollen hat, wird durch eine Beziehung [OneToMany] zur Entität [UserRole] dargestellt;
Die Klasse [Role] ist das Abbild der Tabelle [ROLES]:

- ID: Primärschlüssel;
- VERSION: Versionsspalte der Zeile;
- NAME: Rollenname. Standardmäßig erwartet Spring Security Namen in der Form ROLE_XX, zum Beispiel ROLE_ADMIN oder ROLE_GUEST;
package generic.jpa.entities.dbproduitscategories;
import generic.jdbc.config.ConfigJdbc;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = ConfigJdbc.TAB_ROLES)
public class Role implements AbstractCoreEntity {
// Eigenschaften
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = ConfigJdbc.TAB_JPA_ID)
protected Long id;
@Version
@Column(name = ConfigJdbc.TAB_JPA_VERSIONING)
protected Long version;
@Transient
protected EntityType entityType=EntityType.POJO;
// Eigenschaften
@Column(name = ConfigJdbc.TAB_ROLES_NAME, length = 30, unique = true, nullable = false)
private String name;
// die zugehörigen UserRole
@OneToMany(fetch = FetchType.LAZY, mappedBy = "role", cascade = { CascadeType.ALL })
@JsonIgnore
private List<UserRole> userRoles;
// Konstruktoren
public Role() {
}
public Role(Long id, Long version, String name) {
this.id = id;
this.version = version;
this.name = name;
}
// Getter und Setter
public Role(String name) {
this.name = name;
}
// ------------------------------------------------------------
// Neudefinition von [equals] und [hashcode]
...
// Getter und Setter
...
}
- Zeilen 42–44: Die Tatsache, dass einer Rolle mehrere Benutzer zugeordnet werden können, wird durch eine Beziehung [@OneToMany] zur Entität [UserRole] dargestellt;
Die Klasse [UserRole] ist das Abbild der Tabelle [USERS_ROLES]:

Ein Benutzer kann mehrere Rollen haben, eine Rolle kann mehrere Benutzer umfassen. Es besteht eine Mehr-zu-Mehr-Beziehung, die durch die Tabelle [USERS_ROLES] dargestellt wird.
- ID: Primärschlüssel;
- VERSION: Versionsspalte der Zeile;
- USER_ID: ID eines Benutzers;
- ROLE_ID: ID einer Rolle;
package generic.jpa.entities.dbproduitscategories;
import generic.jdbc.config.ConfigJdbc;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;
@Entity
@Table(name = ConfigJdbc.TAB_USERS_ROLES)
public class UserRole implements AbstractCoreEntity {
// Eigenschaften
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = ConfigJdbc.TAB_JPA_ID)
protected Long id;
@Version
@Column(name = ConfigJdbc.TAB_JPA_VERSIONING)
protected Long version;
@Transient
protected EntityType entityType=EntityType.POJO;
// Ein UserRole verweist auf einen User
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_USER_ID, nullable = false)
private User user;
// Ein UserRole verweist auf eine Rolle
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_ROLE_ID, nullable = false)
private Role role;
// Konstruktoren
public UserRole() {
}
public UserRole(User user, Role role) {
this.user = user;
this.role = role;
}
// ------------------------------------------------------------
// Neudefinition von [equals] und [hashcode]
...
// Getter und Setter
...
}
- Zeilen 34–36: stellen den Fremdschlüssel von der Tabelle [USERS_ROLES] zur Tabelle [USERS] dar;
- Zeilen 38–41: stellen den Fremdschlüssel von der Tabelle [USERS_ROLES] zur Tabelle [ROLES] dar;
20.4.4. Die Ebene [repositories]
![]() |
![]() |
Die Schnittstelle [UserRepository] verwaltet den Zugriff auf die Entitäten [User]:
package spring.security.repositories;
import generic.jpa.entities.dbproduitscategories.Role;
import generic.jpa.entities.dbproduitscategories.User;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, Long> {
// Liste der Rollen eines anhand seiner ID identifizierten Benutzers
@Query("select ur.role from UserRole ur where ur.user.id=?1")
Iterable<Role> getRoles(long id);
// Liste der Rollen eines Benutzers, der durch seine eindeutige Anmelde-ID identifiziert wird
@Query("select ur.role from UserRole ur where ur.user.login=?1 and ur.user.password=?2")
Iterable<Role> getRoles(String login, String password);
// Suche nach einem Benutzer anhand seines Logins
User findUserByLogin(String login);
}
- Zeile 9: Die Schnittstelle [UserRepository] erweitert die Schnittstelle [CrudRepository] von Spring Data (Zeile 7);
- Zeilen 12–13: Die Methode [getRoles(long id)] ermöglicht es, alle Rollen eines Benutzers abzurufen, der durch seine [id] identifiziert wird
- Zeilen 16–17: dasselbe, jedoch für einen Benutzer, der über seinen Benutzernamen und sein Passwort identifiziert wird;
- Zeile 20: zum Auffinden eines Benutzers anhand seines Logins;
Die Schnittstelle [RoleRepository] verwaltet den Zugriff auf die Entitäten [Role]:
package spring.security.repositories;
import generic.jpa.entities.dbproduitscategories.Role;
import org.springframework.data.repository.CrudRepository;
public interface RoleRepository extends CrudRepository<Role, Long> {
// Suche nach einer Rolle anhand ihres Namens
Role findRoleByName(String name);
}
- Zeile 7: Die Schnittstelle [RoleRepository] erweitert die Schnittstelle [CrudRepository];
- Zeile 10: Eine Rolle kann anhand ihres Namens gesucht werden. An dieser Stelle sei daran erinnert, dass die Entität [Role] ein Feld [name] besitzt. Die Methode [findEntityByChamp] wird automatisch von Spring Data implementiert. Es ist daher nicht erforderlich, die Methode [finRoleByName] hier zu implementieren. Sie muss lediglich in der Schnittstelle deklariert werden.
Die Schnittstelle [UserRoleRepository] verwaltet den Zugriff auf die Entitäten [UserRole]:
package spring.security.repositories;
import generic.jpa.entities.dbproduitscategories.UserRole;
import org.springframework.data.repository.CrudRepository;
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
}
- Zeile 7: Die Schnittstelle [UserRoleRepository] erweitert lediglich die Schnittstelle [CrudRepository], ohne ihr neue Methoden hinzuzufügen;
20.4.5. Die Windel [DAO2]
![]() |
![]() |
In der Schicht [DAO2] finden sich dieselben Klassen wie in der Schicht [DAO2] des Projekts [spring-security-server-jdbc-generic], das zuvor in Abschnitt 20.2.2 behandelt wurde. Sie müssen nun lediglich mithilfe der Klassen der Schicht [repositories] implementiert werden.
Die Klasse [AppUserDetails] entwickelt sich wie folgt:
package spring.security.dao;
import generic.jpa.entities.dbproduitscategories.Role;
import generic.jpa.entities.dbproduitscategories.User;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import spring.data.infrastructure.DaoException;
import spring.security.repositories.UserRepository;
public class AppUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
// Eigenschaften
private User user;
private UserRepository userRepository;
// lokal
private String simpleClassName = getClass().getSimpleName();
// Konstruktoren
public AppUserDetails() {
}
public AppUserDetails(User user, UserRepository userRepository) {
this.user = user;
this.userRepository = userRepository;
}
// -------------------------Schnittstelle
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
Iterable<Role> roles;
try {
roles = userRepository.getRoles(user.getId());
} catch (Exception e) {
e.printStackTrace();
throw new DaoException(167, e, simpleClassName);
}
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
...
}
- Zeile 31: Der Konstruktor der Klasse erhält als zweiten Parameter das Objekt [UserRepository], wodurch die Klasse die Rollen eines bestimmten Benutzers abrufen kann (Zeile 42);
Die Spring-Komponente [AppUserDetailsService] entwickelt sich ihrerseits wie folgt:
package spring.security.dao;
import generic.jpa.entities.dbproduitscategories.User;
import org.springframework.beans.factory.annotation.Autowired;
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.Service;
import spring.data.infrastructure.DaoException;
import spring.security.repositories.UserRepository;
@Service
public class AppUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
// lokal
private String simpleClassName = getClass().getName();
@Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
// Der Benutzer wird anhand seines Logins gesucht
User user;
try {
user = userRepository.findUserByLogin(login);
} catch (Exception e) {
throw new DaoException(168, e, simpleClassName);
}
// gefunden?
if (user == null) {
throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
}
// Benutzerdetails werden angezeigt
return new AppUserDetails(user, userRepository);
}
}
- Zeile 18: Einbindung der Spring-Komponente [userRepository], die es dem Dienst ermöglicht, den anhand seines Logins identifizierten Benutzer zurückzugeben (Zeile 27);
Letztendlich stellt sich heraus, dass nur das Repository [userRepository] benötigt wird und nicht die beiden anderen Repositorys [roleRepository, userRoleRepository]. Diese werden im nächsten Projekt verwendet, dessen Ziel es ist, die Tabellen [USERS, ROLES, USERS_ROLES] zu füllen.
20.4.6. Die Tests
Der gesicherte Webdienst wird mit der Konfiguration namens [spring-security-server-jpa-generic-hibernate-eclipselink] [1] gestartet. Der Test [JUnitTestDao] des generischen Clients wird mit der Konfiguration „[spring-security-client-generic-JUnitTestDao] [2]“ gestartet:
![]() |
Die Tests werden erfolgreich ausgeführt.
20.5. Das Eclipse-Projekt [spring-security-create-users]
![]() |
![]() |
20.5.1. Die Datenbank
Durch die Ausführung des Projekts werden die Tabellen [USERS, ROLES, USERS_ROLES] und [dbproduitscategories] gefüllt:

![]() |
![]() |
Die erstellten IDs [login/passwd] lauten wie folgt: [admin/admin], [user/user], [guest/guest]. In der Datenbank sind die Passwörter verschlüsselt.
![]() |

20.5.2. Maven-Konfiguration
Das Projekt ist ein Maven-Projekt, das durch die folgende Datei „[pom.xml]“ konfiguriert wird:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-security-create-users-jpa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-security-create-users-jpa</name>
<description>création de utilisateurs dans la base [dbproduitscategories]</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring-security-server-jpa-generic -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-security-server-jpa-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- Zeilen 22–25: Abhängigkeit vom Spring Security-Framework. Der Algorithmus zur Passwortverschlüsselung wird von diesem Framework bereitgestellt
- Zeilen 27–31: Abhängigkeit vom Projekt [spring-security-server-jpa-generic], das wir gerade erstellt haben. Dieses Projekt implementiert die Schichten [repositories] und [JPA] des Projekts;
Letztendlich ergeben sich folgende Abhängigkeiten:
![]() |
20.5.3. Die Schicht [console]
![]() |
Da die Schichten [repositories] und [JPA] durch die Abhängigkeit [spring-security-server-jpa-generic] implementiert werden, muss nur noch die Schicht [console] implementiert werden.
![]() |
- [AppConfig] ist die Spring-Konfigurationsklasse des Projekts;
- [CreateUsers] ist die ausführbare Klasse, die Benutzer und Rollen anlegt;
- [Base64Encoder] ist eine Hilfsklasse zur Generierung des Base64-Codes für ein [login, password]-Paar. Wir haben sie bereits verwendet. Sie ist für dieses Projekt nicht erforderlich;
Die Spring-Konfigurationsklasse [AppConfig] lautet wie folgt:
package spring.security.install;
import generic.jpa.config.ConfigJpa;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@Configuration
@EnableJpaRepositories(basePackages = { "spring.security.repositories" })
@Import({ ConfigJpa.class })
public class AppConfig {
}
- Zeile 10: Hier wird angegeben, wo sich die [repositories]-Dateien der Anwendung befinden. Sie befinden sich im Paket [spring.security.repositories] der Abhängigkeit [spring-security-server-jpa-generic]
- Zeile 11: Hier werden die Beans der Klasse [ConfigJpa] importiert, die die Schicht [JPA] des Projekts konfiguriert. Diese Klasse befindet sich in der Abhängigkeit [mysql-config-jpa-hibernate]:
![]() |
Die Klasse [CreateUsers] lautet wie folgt:
package spring.security.install;
import generic.jpa.entities.dbproduitscategories.Role;
import generic.jpa.entities.dbproduitscategories.User;
import generic.jpa.entities.dbproduitscategories.UserRole;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.security.crypto.bcrypt.BCrypt;
import spring.security.repositories.RoleRepository;
import spring.security.repositories.UserRepository;
import spring.security.repositories.UserRoleRepository;
public class CreateUsers {
public static void main(String[] args) {
// Ende
System.out.println("Travail en cours...");
// Es werden drei Benutzer angelegt
String[] logins = { "admin", "user", "guest" };
String[] passwds = { "admin", "user", "guest" };
String[] roles = { "admin", "user", "guest" };
// Spring-Kontext
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserRepository userRepository = context.getBean(UserRepository.class);
RoleRepository roleRepository = context.getBean(RoleRepository.class);
UserRoleRepository userRoleRepository = context.getBean(UserRoleRepository.class);
for (int i = 0; i < logins.length; i++) {
// Die Informationen des Benutzers Nr. i werden abgerufen
String login = logins[i];
String password = passwds[i];
String roleName = String.format("ROLE_%s", roles[i].toUpperCase());
// Existiert die Rolle bereits?
Role role = roleRepository.findRoleByName(roleName);
// Falls nicht, wird sie angelegt
if (role == null) {
role = roleRepository.save(new Role(roleName));
}
// Existiert der Benutzer bereits?
User user = userRepository.findUserByLogin(login);
// Falls nicht, wird er angelegt
if (user == null) {
// Das Passwort wird mit bcrypt gehasht
String crypt = BCrypt.hashpw(password, BCrypt.gensalt());
// Der Benutzer wird gespeichert
user = userRepository.save(new User(null, null, login, login, crypt));
// Die Verknüpfung mit der Rolle wird hergestellt
userRoleRepository.save(new UserRole(user, role));
} else {
// Der Benutzer existiert bereits – hat er die angeforderte Rolle?
boolean trouvé = false;
for (Role r : userRepository.getRoles(user.getId())) {
if (r.getName().equals(roleName)) {
trouvé = true;
break;
}
}
// Wenn nicht gefunden, wird die Verknüpfung mit der Rolle erstellt
if (!trouvé) {
userRoleRepository.save(new UserRole(user, role));
}
}
}
// Spring-Kontext schließen
context.close();
// Ende
System.out.println("Travail terminé...");
}
}
- Zeilen 22–24: Definieren den Benutzernamen, das Passwort und die Rolle von drei Benutzern;
- Zeile 27: Der Spring-Kontext wird anhand der Konfigurationsklasse [AppConfig] aufgebaut;
- Zeilen 28–30: Es werden die Referenzen der drei [Repository] abgerufen, die uns beim Anlegen eines Benutzers nützlich sein können;
- Zeile 31: Die drei Benutzer werden angelegt;
- Zeilen 33–35: Die Informationen zum Anlegen des Benutzers Nr. i;
- Zeile 37: Es wird geprüft, ob die Rolle bereits existiert;
- Zeilen 39–41: Ist dies nicht der Fall, wird sie in der Datenbank angelegt. Sie erhält einen Namen vom Typ [ROLE_XX];
- Zeile 43: Es wird geprüft, ob das Login bereits existiert;
- Zeilen 45–52: Falls der Benutzername noch nicht existiert, wird er in der Datenbank angelegt;
- Zeile 47: Das Passwort wird verschlüsselt. Hier wird die Klasse [BCrypt] von Spring Security (Zeile 8) verwendet. Daher werden die Archive dieses Frameworks benötigt. Die Datei [pom.xml] enthält diese Abhängigkeit:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- Zeile 49: Der Benutzer wird in der Datenbank gespeichert;
- Zeile 51: ebenso wie die Beziehung, die ihn mit seiner Rolle verbindet;
- Zeilen 55–60: Falls der Benutzer bereits existiert – wird geprüft, ob die Rolle, die ihm zugewiesen werden soll, bereits zu seinen Rollen gehört;
- Zeile 62–64: Wenn die gesuchte Rolle nicht gefunden wurde, wird eine Zeile in der Tabelle [USERS_ROLES] angelegt, um den Benutzer mit seiner Rolle zu verknüpfen;
- Es wurde keine Absicherung gegen mögliche Ausnahmen vorgenommen. Es handelt sich um eine Hilfsklasse zum schnellen Anlegen von Benutzern;
Um das Projekt auszuführen, wird die Ausführungskonfiguration mit dem Namen [spring-security-create-users-hibernate-eclipselink] ausgeführt:
![]() |
Wir haben soeben zwei sichere Webdienste erstellt:
- das eine mit der Architektur [security / web / JDBC / MySQL];
- der andere mit der Architektur [security / web / Hibernate / MySQL];
Wir befassen uns nun mit zwei weiteren Architekturen:
- eine Architektur [security / web / EclipseLink / SQL Server 2014 Express];
- eine Architektur [security / web / OpenJpa / Oracle Express];
20.5.4. Architektur [security / web / EclipseLink / SQL Server]
![]() |
![]() |
- In [1] werden die Projekte geladen, die eine Schicht [JDBC / SQL Server] und eine Schicht [JPA / EclipseLink / SQL Server] konfigurieren;
Hinweis: Drücken Sie Alt-F5 und generieren Sie anschließend alle Maven-Projekte neu.
Es wird davon ausgegangen, dass der Server SGBD SQL gestartet ist und die Datenbank [dbproduitscategories] generiert wurde. Zunächst müssen wir die Tabellen [USERS, ROLES, USERS_ROLES] dieser Datenbank füllen. Führen Sie dazu die Ausführungskonfiguration mit dem Namen [spring-security-create-users-hibernate-eclipselink] aus:
![]() | ![]() |
Dadurch werden die drei Tabellen mit Daten gefüllt:
![]() | ![]() |
![]() |
![]() |
- Starten Sie den gesicherten Webdienst mit der Konfiguration namens [spring-security-server-jpa-generic-hibernate-eclipselink][1];
- Führen Sie den Test JUnitTestDao mit der Konfiguration namens [spring-security-client-generic-JUnitTestDao][2] aus. Er muss erfolgreich sein: [3];
![]() |
![]() |
20.5.5. Architektur [security / web / OpenJpa / Oracle Express]
![]() |
![]() |
- In [1] werden die Projekte geladen, die eine Ebene [JDBC / Oracle Express] und eine Ebene [JPA / OpenJpa / Oracle Express] konfigurieren;
Hinweis: Drücken Sie Alt-F5 und generieren Sie anschließend alle Maven-Projekte neu.
Es wird davon ausgegangen, dass Oracle Express (SGBD) gestartet ist und die Datenbank ([dbproduitscategories]) generiert wurde. Zunächst müssen wir die Tabellen ([USERS, ROLES, USERS_ROLES]) dieser Datenbank füllen. Führen Sie dazu die Ausführungskonfiguration mit dem Namen [spring-security-create-users-openjpa] aus:
![]() | ![]() |
Dadurch werden die drei Tabellen mit Daten gefüllt:
![]() | ![]() |
![]() |
![]() |
- Starten Sie den gesicherten Webdienst mit der Konfiguration namens [spring-security-server-jpa-generic-openjpa][1-2];
- Führen Sie den Test JUnitTestDao mit der Konfiguration namens [spring-security-client-generic-JUnitTestDao][3] aus. Er muss erfolgreich sein: [4];
![]() |
![]() |



























































































