This is a simple Spring Boot application. Here you find the following examples:
- RestController
- Spring Security
- Spring JPA support
- Spring-Boot-Devtools. Automatic classpath reload on change.
git clone git@github.com:gluehloch/springboot-demo.git springboot-demo.git
cd springboot-demo.git
mvn clean package spring-boot:run
Unter der Adresse http://localhost:8080/demo/ping
gibt der
Server das aktuelle Datum und Uhrzeit aus. Mit den Command-Line
Werkzeugen httpie oder curl bekommt man eine Ausgabe auf der Kommandozeile:
http http://localhost:8080/demo/ping
curl http://localhost:8080/demo/ping
User anlegen:
curl -X POST --header 'Content-Type: application/json' \
-d '{ "nickname": "Frosch", "name": "Winkler", "firstname": "Andre", "password": "password" }' \
'http://localhost:8080/user'
Alle User abfragen:
curl http://localhost:8080/user
Einloggen:
curl -X POST --header 'Content-Type: application/json' \
'http://localhost:8080/login?nickname=Frosch&password=password'
http://localhost:8080/v3/api-docs
Das Projekt baut gegen Spring 3.1.4
Die Spring-Boot Anwendung runterfahren:
curl -X POST localhost:8080/actuator/shutdown
Im ersten Entfwurf leite ich die Entities direkt aus der REST Schnittstelle raus. Als Beispiel taugt die UserEntity ganz gut. Diese habe ich später um Beziehungen zu Rollen und Privilegien erweitert, die selbst Rückbeziehungen zur UserEntity unterhalten. Bei der Serialisierung kommt es dann zu den typischen Problemen, dass eine rekursive Struktur serialisert werden muss.
Zu dem Thema gibt es zwei Meinungen. Z.B.
Beide Begründungen sind einleuchtend. Wie immer kommt es auf den Anwendungsfall an. In simplen Fällen ist die Lösung von Adam Bien völlig ausreichend. Werden die Dinge komplizierter, weil die Serialisierung schwieriger wird, empfehlen sich spezielle DTO Klassen, wie bei Torben Jansen beschrieben.
Ich bin eher ein Fan der strikten Trennung von JSON und Entity Repräsentationen. Man kommt relativ schnell in die Verlegenheit JSON Annotationen zu verwenden. Gemischt mit den JPA Annotationen ergibt das ein schiefes Bild im Code-Editor in meinen Augen.
In dem Package de.winkler.springboot.user
versuche ich den folgenden Ansatz:
UserController
: Alle Rückgabeparameter sind einfache Klassen mit setter und getter. Falls Relationen abgebildet werden müssen, dann maximal in einer 1:N Ausprägung. Das gleiche gilt für die Eingabeparameter. Die Klassen sind mit dem PostfixJson
markiert. Der Controller ist für das Konvertieren von Entities zu Json zuständig.UserService
: Wird vomUserController
verwendet. DerUserService
kennt keineJson
Typen. Der*Service
spricht nur mit Entities. Bzw. mit Typen der ArtNickname
.UserRepository
: Kommunikation mit der Datenbank.
TODO: Zur Zeit bin ich der Meinung, dass der Controller keine Konvertierung von Entity zu reinen 'JSON' Klassen vornehmen sollte. Falls DTO/JSON Klassen benötigt werden, sollten diese direkt aus der Query heraus angelegt werden. Das reduziert für Hibernate die Komplexität der Query, da keine zu cachenden Entities angelegt werden müssen. Ebenso entfällt für Hibernate die 'Lifecycle' Betrachtung. Am Ende sind reine DTO-Queries günstiger als Entity-Queries. Insbesondere wenn das Ergebnis nur für einen lesenden Zugriff benötigt wird.
Keine Exceptions. Keine NULL-Referenzen. Funktionaler Code. Kann der Code besser lesbar sein? Im Package 'order' werde ich diese Idee umzusetzen.
Wo kommen die @PreAuthorized
und @RolesAllowed
Annotationen hin? Service oder Controller Ebene?
- Authentication: Wer startet den Request?
- Authorization: Welche Berechtigungen hat der Request?
de.winkler.springboot.SecurityConfiguration
Konfiguriert Spring´s HttpSecurity.
Im Verzeichnis ./src/test/resources/de/winkler/springboot/jwt
findet sich eine KeyStore Datei awtest.jks
. In einem produktiven System würde man die KeyStore Datei vielleicht nicht parallel zum Code in einem Repository halten.
Mit dem folgenden Befehl wird ein KeyStore angelegt:
keytool -genkey -alias awtest -keyalg RSA -keystore awtest.jks -keysize 2048
Ein Zertifikat (Public-Key) kann exportiert werden:
keytool -export -keystore awtest.jks -alias awtest -file awtest.cer
- Anzeigen aller Zertifikate/Schlüssel
keytool -list -keystore <keystorefilename> -storepass <keystorepassword> keytool -list -keystore awtest.jks -storepass awtest666
Im Code findet sich ein Beispiel (Klasse KeyStoreService
), wie man aus
der Applikation heraus direkt auf das KeyStore zugreift.
@DisplayName
Erlaubt einen aussagekräftigen Anzeigenamen.@Tag
Damit können Tags/Labels vergeben werden. In den Tests habe ich Tags wie 'Repository', 'Service' und 'Controller' verwendet. Das ist redundant, da meine Test Klassen 'UserControllerTest' oder 'UserRepositoryTest' heißen. Hier fehlt die fachliche Bedeutung. I.d.R. ein Hinweis, dass nicht viel 'Fachlichkeit' im Code zu finden ist.
RestAssured für das Testen von REST Services. Die Syntax kann ich mir nur schwer merken. Vielleicht verwende ich das Tool zu wenig.
Die Entitäten UserEntity
, RoleEntity
und PrivilegeEntity
sind per @ManyToMany
Relation miteinander verbunden. Einer Rolle sind verschiedene Privilegien zugeordnet. Die Rollen werden wiederum Anwendern zugeordnet.
- Wie sind die N:N Beziehungen zu handhaben? Sollte es eine Master-Entity geben, die die Bearbeitung steuert?
- Es ist zu fragen, ob man diese Art der Klassenbeziehungen
in seiner Java Welt benötigt. Aus Performance Sicht ist diese
Struktur nicht ideal. Denkbar wäre ein DTO, welches
die Eigenschaften aus
UserEntity
,RoleEntity
undPrivilegeEntity
vereint.
Hier habe ich mich für Constructor-Injection entschieden.
Erzeugt mehr Schreibaufwand. Dafür kann man die Felder
final
deklarieren. Bin mir nicht sicher, ob das ein
Vorteil ist.
Zu finden unter der lokalen Adresse.
Ich generiere mir einen Public/Private Key. Den 'public key' publiziere ich auf einem Server oder gebe diesen einer bestimmten Person. Bei dem Server melde ich mich mit dem 'public key' an. Die Person verschlüsselt eine Nachricht mit dem 'pubic key' und sendet mir diese Nachricht. Ich kann mit dem 'private key' die Nachricht entschlüsseln und lesen.
- Speicherung im Klartext ist keine Lösung.
- Für ein verschlüsseltes Password gibt es keinen Rückweg. D.h. es kann nicht wieder entschlüsselt werden, um es im Klartext zu lesen.
- Deswegen wöre ein String-Vergleich von Passwörtern keine gute Lösung.
- Public/Private key for token creation/validation.
- Public/Private key reading from KeyStore or certificate file.
- Spring Security einbauen (LoginService verwenden).
- Spring Security Automatismus für die URL /logout.
- Beispiel für die Verwendung von @Authorized Annotation.
- Problem: Zugangsgeschütze Seite mit redirect auf '/login'.
- Rollen anlegen.
- BCrypt zum sicheren Ablegen von Passwörtern als Hash in der Datenbank verwendet wird. In dem Zusammenhang: Was ist Vault?
- OpenAPI (Swagger)
- OpenID
- Beispiel: REST, JPA-Entity, DTO oder besser ohne?
- Spring Data JPA: Pageable. Inklusive Weiterreichung an das Frontend.
- Angular Demo Anwendung mit Login/Logout und automatischer Umleitung auf login.
Englisch | Deutsch |
---|---|
authority | Befugnis / Berechtigung / Autorität |
authentication | Authentifizierung |
authorization | Autorisierung / Ermächtigung |
credentials | Zugangsdaten |
principal | Hauptinhaber |