Teil V: Authentisierung und Autorisierung

Serverless versus Microservices: Ist AWS Lambda das bessere Spring Boot?

Oliver Wronka

(c) Shutterstock / ketzal01

 

Führt der Trend zu Microservices direkt zum Serverless Computing à la AWS Lambda? Dieser Frage gehen wir in dieser Artikel-Reihe nach. Im abschließenden Teil der Serie kümmern wir uns um das Thema Authentisierung/Autorisierung, bevor wir im Fazit die Eingangsfrage beantworten: Ist AWS Lambda nun das bessere Spring Boot?

AWS Lambda – das bessere Spring Boot?

Bisher sind bereits einige Artikel zu AWS Lambda sowie dem API Gateway erschienen. Eine wichtige Fragestellung wurde bisher aber noch nicht beantwortet: Ist Serverless die kommende Ablösung der Microservice-Architektur, insbesondere, wenn Java zum Einsatz kommt?

Diese Artikelreihe versuche ich eine Antwort auf diese Frage zu geben. Dafür soll ein vollständiges Beispiel für eine auf AWS Lambda betriebene Webanwendung implementiert werden. Ergänzende Informationen wie z.B. ausführliche Listings habe ich auf meinem Blog http://java-goes-serverless.blog/ hinterlegt.

Ist AWS Lambda das bessere Spring Boot?
Teil I: Setup und Persistenz
Teil II: Logik
Teil III: Von Lambda zu Lambda
Teil IV: Präsentation
Teil V: Authentisierung und Autorisierung

 

5. Authentisierung und Autorisierung

Authentisierung und Autorisierung ist das aus meiner Sicht spannendste Kapitel in dieser Beispielanwendung. Insgesamt hat es mich auch am meisten Zeit gekostet, zumal die dazugehörige Dokumentation weit verstreut ist. Erschwerend kommt hinzu, das Amazon das generelle Tokenhandling, das ich hier beschreiben werde, einmal überarbeitet hat. Während man sich also durch die Online-Artikel wühlt, muss man sich immer wieder fragen, ob man sich noch die veraltete oder schon die brandneue Version des Tokenhandlings erarbeitet.

In der bestehenden Microservice-Lösung basierte der Aspekt der Authentisierung und Autorisierung auf einem selbst implementierten OAuth2-Microservice. Hierzu hatte ich im Login-Microservice eine Tokengenerierung, -verwaltung sowie –validierung implementiert. Aber auch hier hat Amazon eine Lösung parat, und diese lautet Amazon Cognito.

Um hier einmal kurz zu zitieren:

Mit Amazon Cognito können Sie problemlos Benutzerregistrierung und -anmeldung zu Ihren mobilen und Web-Apps hinzufügen. Mit Amazon Cognito verfügen Sie zudem über die Optionen zur Authentifizierung von Benutzern mittels Social Identity-Anbieter wie Facebook, Twitter oder Amazon, mit SAML-Identitätslösungen oder durch Einsatz Ihres eigenen Identitätssystems. Außerdem ermöglicht Ihnen Amazon Cognito das lokale Speichern von Daten auf Benutzergeräten, sodass Ihre Anwendungen auch dann funktionieren, wenn die Geräte offline sind. Weiterhin können Sie Daten auf allen Geräten eines Benutzers synchronisieren, sodass seine Erfahrungen mit der App unabhängig vom genutzten Gerät gleich bleiben.“

Hört sich genau nach dem an, was wir benötigen. Allerdings haben wir ja bereits Stammdaten in unserem laufenden Microservice, die wir gerne nutzen möchten. Daher habe ich einen alternativen Weg verfolgt. Dieser basiert darauf, dass Amazon einen Service anbietet, der Token ausstellt. Diese Token kann man dann in seiner Anwendung nutzen, um Zugriff auf Amazon-Dienste zu erhalten, z.B. das API-Gateway.

Lesen Sie auch: Die Serverless Cloud – eine Einführung: AWS Lambda und das Serverless Framework

Dieser Service lautet AWS Security Token Service (STS). Die Idee dahinter ist bestechend einfach. Mittels eines technischen Users greift man von einer Lambda-Methode (es liegt nahe, hier die Login Lambda-Methode zu nehmen) auf STS zu und gibt, nachdem man den User mittels der eigenen Stammdaten verifiziert hat, ein Token an die Webanwendung (oder MobileApp) zurück. Der Nachteil ist, dass es kein einziges, durchgängiges Beispiel hierzu gibt, insbesondere unter Java. Das ist ab heute anders.

Hier also der Code, um eine Verbindung zum STS aus der Lambda-Methode heraus aufzubauen:

public static AWSSecurityTokenService getSTS() {
if (sts == null){
		sts = AWSSecurityTokenServiceClientBuilder.
			  standard().
			  withCredentials(new 
			  AWSStaticCredentialsProvider(creds)).
			  withEndpointConfiguration(new 
			  EndpointConfiguration(
  "https://sts.eu-central-1.amazonaws.com", "eu-central-1")).
		  build();
	}
	return sts;
}


Und hier der Code, um das Token zu generien:

public CredentialsTO getToken () {
    	GetSessionTokenRequest gstRequest = new GetSessionTokenRequest();
	gstRequest.setDurationSeconds(SESSION_DURATION);

	GetSessionTokenResult gstResult = 
AmazonService.getSTS().getSessionToken(gstRequest);
		
	return new CredentialsTO(gstResult.getCredentials().getAccessKeyId(),
gstResult.getCredentials().getSecretAccessKey(),
			gstResult.getCredentials().getSessionToken(),
		      gstResult.getCredentials().getExpiration());
}


Wir benötigen also ganze 20 Zeilen Code, um ein Token zu generieren, mit dem man auf sein geschütztes API-Gateway zugreifen kann.

Damit das auch funktioniert, muss man das API-Gateway entsprechend konfigurieren. Man kann je Methode des API-Gateways angeben, ob eine Autorisierung zuvor erfolgen soll. Dies geschieht, indem man die entsprechenden Methoden wie folgt absichert:

AWS Lambda Spring Boot

Lambda-Handler Autorisierung einrichten

In dem hier gezeigten Fall konfiguriere ich die POST-Methode der URL /content/area so, dass diese ein Token benötigt, um ausgeführt zu werden.

So einfach das Erzeugen eines Token ist – das Absichern eines Requests ist ungleich aufwändiger. Auch hier gibt es kein durchgängiges Beispiel für den vierstufigen Vorgang, bis jetzt.

Die eigentliche Beschreibung des Verfahrens würde den Umfang dieser Serie sprengen. Die entsprechende Dokumentation ist aber in Amazon vorhanden. In meinem Blog befindet sich ein vollständiges Beispiel für das Anlegen einer Area unter Verwendung eines Token in Java sowie JavaScript.

Fazit

Ist AWS Lambda nun das bessere Spring Boot? Die Antwort wird überraschen, aber aktuell ist es das nur eingeschränkt! Hier erst einmal die Punkte, die richtig gut funktionieren:

  • Keinen direkten oder indirekten Kontakt zur unterliegenden Plattform, also nur coden: Check!
  • Skaliert nahezu beliebig (Ausnahme relationale Datenbank): Check!
  • HTTPS out of the Box: Check!

Aber es gibt ein paar hässliche Stellen, die die Sinnhaftigkeit der Lösung aktuell noch in Frage stellen. Da wäre zum einen die Verwaltung der DB-Connection innerhalb eines Lambdahandlers. Lambdas haben aktuell noch keine setUp/tearDown-Methoden die seitens der Laufzeitumgebung aufgerufen werden. Somit muss man seine DB-Connection mit jedem Call neu aufsetzen und mit Beendigung des Lambdahandlers auch wieder schließen. Es wäre viel schöner, wenn man die DB-Connection mit der Instanziierung eines Lambdahandlers aufmachen und erst dann wieder schließen müsste, wenn die Laufzeitumgebung beschließt, meinen Lambdahandler aus dem RAM zu schmeißen.

Das alleine wäre aber noch kein Showstopper. Viel schlimmer ist das Verhalten des Lambdahandlers, wenn dieser kalt angesprochen wird. Unter kalt verstehe ich, dass der Lambdahandler erst geladen werden muss, bevor dieser dann ausgeführt wird. Selbst bei einer noch so kleinen Lambdamethode dauert es bis zu 8 Sekunden, bis diese antwortet. Ein anschließender, zweiter Call auf einen geladenen Lambdahandler geht dann deutlich schneller, meist im dreistelligen Millisekundenbereich. Man kann die Symptomatik ein wenig lindern, indem man in der Konfiguration für die Lambdafunktion den maximalen Wert für den zu reservierenden Speicher einträgt, in diesem Fall also 1536 MB.

Lambda-Handler Konfiguration

Das erscheint erst einmal widersinnig, insbesondere, wenn man sich den dazugehörigen Eintrag in CloudWatch ansieht:

CloudWatch-Eintrag zur Ressourcennutzung des Login Lambda-Handlers

Laut diesem Logeintrag benötigte die Methode zur Ausführung nur 104 MB. Warum sollte ich dann 1,5 GB zur Verfügung stellen? Diese Thematik wurde in diversen Foren diskutiert. Tatsächlich reduziert sich die Ausführungszeit einer Lambdamethode abhängig vom konfigurierten RAM. Je mehr RAM desto geringer die Lade- und Ausführungszeit.

Ich habe also einmal eine kleine Messreihe aufgelegt, um die Ausführungszeiten abhängig zum RAM darzustellen:

Memory Cold Hot
1536 5190 161
1280 5697 131
1024 6924 173
768 8350 172
512 10236 512
256 17133 1861

Ausführungszeiten (Kalt, Warm) abhängig vom RAM

Die einzig sinnvolle Erklärung für diese Werte ist, dass die bereitgestellte CPU-Kapazität abhängig ist vom konfigurierten RAM. Mit diesen Erkenntnissen kann man seine Anwendung nun doch noch so trimmen, dass ein passables Antwortverhalten entsteht. Der einfachste Weg wäre, alle Lambdas einfach jede Stunde einmal anzusprechen (das ist ungefähr die Zeit, die Amazon eine Lambda zur direkten Wiederausführung im RAM belässt) – intelligent ist das aber nicht.

Mit ein wenig Hirnschmalz kann man die Situation entschärfen. Schauen wir uns einmal das typische Nutzerverhalten unserer  Minianwendung an. In der Regel dürfte der Anwender die Einstiegsseite aufrufen, sich einloggen und anschließend auf den Content zugreifen. Die Registrierung hingegen führt er nur einmal durch. Mit diesem anwendungsspezifischen Wissen kann man nun die Lambdas „vorglühen“. Sobald die Einstiegsseite aufgerufen wird, werden die Lambdas für das Login sowie das Contenthandling direkt ohne gültige Werte asynchron angesprochen. Der Nutzer selbst muss ja erst einmal die Login-Seite aufrufen und seine Credentials aufrufen. Bis dahin sind die beiden Lambdas warm und antworten beim nächsten Aufruf deutlich schneller.

Bei der Registrierung hingegen ist es so, dass ich die Methode erst dann anwärme, wenn der Nutzer die Registrierungsseite tatsächlich aufruft. Er ist eh noch eine Weile damit beschäftigt, seine Angaben einzutragen. Somit bleibt mir genügend Zeit, die Methode erst mit dem Aufruf der Registrierungsseite asynchron anzuwärmen.

Wie gut das funktioniert, kann jeder unter https://axx2sls.de selbst nachschauen. Und wer genau wissen will, wie das alles funktioniert, der schaut bitte hier nach: https://github.com/owronka/axx3sls.

Soweit der Ausflug ins AWS-Lambda-Land. Ich hoffe, es hat Spaß gemacht und genügend Anregungen geliefert, diesen neuen Technologie-Trend einmal selbst aufzugreifen.

Mehr zum Thema:

Microservices-Applikationen in der AWS Cloud

 

 

Geschrieben von
Oliver Wronka
Oliver Wronka
Oliver Wronka befasst sich mit Java seit der Version 1.0. Schwerpunkt in seiner Funktion als Principal-Softwarearchitekt bei der axxessio GmbH mit Sitz in Bonn sind Java-basierte Backend-Systeme.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.