Write Entities and JWT

This commit is contained in:
Marcel Schwarz 2020-06-10 20:20:23 +02:00
parent 1fce986a2d
commit d0cae8af87
3 changed files with 69 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -25,8 +25,76 @@
Das Zweite wichtige Programm war der REST-Client Insomnia REST\footnote{\url{https://insomnia.rest/}}, welcher alle Möglichkeiten bietet, um REST APIs zu testen und Testabfragen auszuführen. Das Zweite wichtige Programm war der REST-Client Insomnia REST\footnote{\url{https://insomnia.rest/}}, welcher alle Möglichkeiten bietet, um REST APIs zu testen und Testabfragen auszuführen.
\section{Umsetzung} \section{Umsetzung}
\subsection{Spring Entities} \subsection{Spring Entities}
\missingfigure{Datenbankmodell (Entitiy Relationship Modell)} \begin{figure}[H]
\centering
\includegraphics[width=\linewidth]{img/backend/er-modell.png}
\caption{ER-Modell}
\label{fig:er-modell}
\end{figure}
Das ER-Modell in Abbildung \ref{fig:er-modell} zeigt die komplette Hierarchie wie sie unserem Konzept entspricht. Wir legen diese Definition aber nicht selbst in SQL an, sondern lassen Java Hibernate dies für uns tun. Die Grundstruktur der gespeicherten Daten ist wie folgt zu verstehen:
\begin{itemize}
\item Der \textbf{TimetrackUser} ist die Grundstruktur, die alle anderen Daten des Users zusammenhält. Sie speichert allgemeine Nutzerdaten und hält Referenzen auf die \textbf{Role} des Nutzers, seine \textbf{Location} und alle im gehörenden \textbf{TimetrackAccounts}.
\item Die \textbf{Role} sollte ursprünglich erlauben zwischen eines Admins und eines normalen Nutzers zu unterscheiden, aus Zeitgründen wurde dies aber weggelassen. Die Grundstruktur ist dennoch implementiert, allerdings so, dass jeder Nutzer automatisch Administrator ist.
\item Die \textbf{Location} Entität speichert den Geofence des Nutzers. Diese Daten werden ausschließlich von der Android App genutzt um beim Einloggen den Geofence zu setzen.
\item Der \textbf{TimetrackAccount} ist die zweite große Struktur, die alle \textbf{TimeRecords} des Nutzers verwaltet, jeder Nutzer kann mehrere \textbf{TimetrackAccounts} besitzen, aber jeder Account kann nur einem Nutzer gehören.
\item Jede getrackte Zeitspanne wird in einem \textbf{TimeRecord} abgespeichert. Dieser Record speichert einen Typ sowie das Start- und Enddatum. Der Typ kann entweder "PAID" oder "BREAK" sein. Jeder Record gehört zu genau einem TimetrackAccount.
\end{itemize}
Die Umsetzung in Java wird nun am Beispiel des TimetrackUsers und des TimetrackAccounts gezeigt.
\lstinputlisting[language=Java,caption=TimetrackUser,firstline=12]{../backend/src/main/java/de/hft/geotime/entities/TimetrackUser.java}
Die komplette Klasse ist durch die Lombok Integration sehr klein gehalten. Alles weitere wird durch Annotationen geregelt, einige Beispiele sind hier:
\begin{itemize}
\item[] \textbf{@Entity} markiert die Klasse als Speicherbar in der Datenbank.
\item[] \textbf{@ManyToOne} markiert das Attribut als Fremdschlüssenrelation aus einer anderen Tabelle.
\item[] \textbf{@Id} zeichnet des Primärschlüssel der Tabelle aus.
\item[] \textbf{@Column} setzt spezielle Attribute für die Spalte in der Datenbank.
\end{itemize}
Die TimetrackAccounts haben zusätzlich noch die Eigenschaft, dass sie sich selbst rekursiv löschen wenn der zugehörige User gelöscht wird. Selbiges gilt auch für die Records, wenn der zugehörige Account gelöscht wird.
\lstinputlisting[language=Java,caption=TimetrackAccount,firstline=12]{../backend/src/main/java/de/hft/geotime/entities/TimetrackAccount.java}
\subsection{Sicherheit durch JWT} \subsection{Sicherheit durch JWT}
Da wird die Web App im laufe des Projekts auch öffentlich in Internet stellen mussten, war eine Art Authentifizierung so gut wie unumgänglich. Damit wird keine Probleme mit Session-Affinity haben, entschieden wir uns für eine Token-Based Authentifizierung. Bei der genauen Implementation handelt es sich hier um das Json Web Token, kurz JWT.
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth]{img/backend/jwt.io.png}
\caption{Aufbau eines JWT}
\label{fig:aufbau-jwt}
\end{figure}
In Abbildung \ref{fig:aufbau-jwt} ist ein exemplarischer Aufbau eines JWT dargestellt. Das JWT besteht grundsätzlich aus drei Teilbereichen:
\begin{enumerate}
\item \textbf{Rot hinterlegt:} Bei diesem Teil handelt es sich um den Header, dieser beinhaltet den Typ des Tokens, als auch den Algorithmus mit dem es verschlüsselt wurde.
\item \textbf{Lila hinterlegt:} In diesem Teil werden die eigentlichen Nutzdaten des Tokens abgelegt, dort können z.B. Nutzernamen oder Nutzer-Id sowie eine Rolle hinterlegt werden.
\item \textbf{Blau hinterlegt:} Der letzte Part ist dann noch die Signatur des Tokens.
\end{enumerate}
Jeder dieser Teile ist durch einen Punkt im Token abgetrennt. Es ist daher nicht wunderlich, dass alle Token das selbe Präfix haben werden und nur der Mittelteil, sowie die Signatur sich ändern.
Die Implementation in Spring Boot gelang in drei, vergleichsweise einfachen, Schritten. Zunächst mussten einige Konstanten definiert werden, zur einfacheren Handhabung wurde auch das Secret in den Code platziert. Dieses könnte aber sehr leicht über eine Umgebungsvariable überschrieben werden.
\lstinputlisting[language=Java,caption=JWT Security Constants]{../backend/src/main/java/de/hft/geotime/security/SecurityConstants.java}
Die Lebensdauer eines Tokens wurde mit 10 Tagen ebenfalls sehr hoch gewählt, um die Entwicklung zu vereinfachen. Auch muss dem Token zur erfolgreichen Nutzung in anderen Systemen das Prefix "Bearer " vorangestellt werden.
Um nun die Tokens in Java zu erzeugen und Abzugleichen, musste die Filterkette von Spring Boot, welche bei jeden Request durchlaufen wird, bearbeitet werden. Jeder Endpunkt außer "/login" und "/sign-up" benötigten ab diesen Zeitpunkt eine autorisierte Anfrage.
\lstinputlisting[language=Java,linerange={30-48},caption=JWT Authentication Filter,label=code:jwt-authentication-parse]{../backend/src/main/java/de/hft/geotime/security/JWTAuthenticationFilter.java}
In Listing \ref{code:jwt-authentication-parse} ist der Schritt zu sehen, der die ankommende Anfrage versucht in eine Loginanfrage zu parsen. Diese Anfrage wird dann in der Filterkette weitergereicht. Bis Sie zum UserDetailsService kommt, welcher den User in der Datenbank abfragt und auch das Password abgleicht. Sollte die interne Autorisation erfolgreich sein, wird dieses Objekt mit den Nutzerdaten wieder an die Filterkette zurückgegeben und landet schließlich bei Listing \ref{code:jwt-authentication-create}.
\lstinputlisting[language=Java,linerange={50-62},caption=JWT Authentication Filter,label=code:jwt-authentication-create]{../backend/src/main/java/de/hft/geotime/security/JWTAuthenticationFilter.java}
Der letzte Schritt ist dann nur noch das Token mit den erhaltenen Daten zu befüllen und dann den "Authorization" Header der Antwort auf das soeben erstelle Token zu setzen.
Ab jetzt kann sich der Client der das Token angefragt hat für die nächsten 10 Tage damit authentifizieren. Dies läuft sehr ähnlich ab, deshalb hier nur sehr kurz dargestellt.
\lstinputlisting[language=Java,linerange={40-55},caption=JWT Authorization Filter,label=code:jwt-authorization]{../backend/src/main/java/de/hft/geotime/security/JWTAuthorizationFilter.java}
Der eingehende Request geht wieder durch die Filterkette und wenn er an dem Filter in Listing \ref{code:jwt-authorization} ankommt, wird der User extrahiert und später im Security Manager als Autorisation für diesen Request gesetzt. Wichtig ist hier, dass keine weitere Prüfung auf die Existenz des Users durchgeführt wird, auch das Password wird nicht nochmal abgefragt. Der Grund hierfür ist, wenn es den User nicht geben würde, wie käme er dann an das Token?
\subsection{Repositories} \subsection{Repositories}
\subsection{Projections} \subsection{Projections}
\section{Endpoints} \section{Endpoints}