Mit Version 25.2 legt Vaadin die zweite Funktionsversion innerhalb der 25.x-Linie vor. Die Veröffentlichung versammelt eine Reihe von Neuerungen, die von KI-gestützten Datenansichten über den Zugriff auf native Browserfunktionen aus reinem Java bis hin zu Werkzeugen für Lasttests reichen. So bemerkenswert diese Erweiterungen für die tägliche Entwicklungsarbeit sein mögen, richtet der vorliegende Beitrag das Augenmerk bewusst auf einen Aspekt, der in der Außenwahrnehmung leicht hinter den auffälligeren Funktionen zurücktritt, in seiner Tragweite jedoch kaum zu überschätzen ist: die Härtung der sicherheitsrelevanten Voreinstellungen.
Table of Contents
- Das Fundament: Warum Vaadins serverseitige Architektur als inhärent sicher gilt
- Schutz vor unsicheren URL-Schemata (neu in 25.2)
- Sichere Darstellung nicht vertrauenswürdigen HTML mit Safelist (neu in 25.2)
- Clickjacking-Schutz durch X-Frame-Options: SAMEORIGIN (neu in 25.2)
- Härtung der Lieferkette gegen kompromittierte npm-Pakete (neu in 25.2)
- Der bestehende Schutzschild im Überblick, in den sich die Neuerungen einfügen
- Sichere Konfiguration und Betrieb
- Migrationshinweise: sicherheitsbedingte Verhaltensänderungen beim Upgrade auf 25.2
- Fazit: Sicherheit als Voreinstellung und ihre Bedeutung für Compliance-Anforderungen
Die leitende These dieses Beitrags lautet, dass die sicherheitsbezogenen Änderungen in 25.2 keine Ansammlung isolierter Einzelmaßnahmen bilden, sondern einem gemeinsamen Gestaltungsprinzip folgen. Sie verkörpern einen Übergang hin zu sicheren Voreinstellungen, im englischen Sprachgebrauch als „secure by default“ bezeichnet. Schutzmechanismen, die zuvor einer ausdrücklichen Aktivierung durch den Entwickler bedurften und deren Unterlassung in der Praxis eine verbreitete Fehlerquelle darstellte, greifen nunmehr ohne weiteres Zutun. Das Sicherheitsniveau einer Anwendung hängt damit weniger von der Sorgfalt einzelner Konfigurationsentscheidungen ab und ruht stärker auf einem Fundament, das das Framework selbst bereitstellt.
Diese Verschiebung ist von besonderer Bedeutung für Organisationen, die regulatorischen oder vertraglichen Sicherheits- und Compliance-Anforderungen unterliegen. Wo Schutzmaßnahmen nicht eigens eingerichtet werden müssen, sondern im Auslieferungszustand bereits wirksam sind, verringert sich die Wahrscheinlichkeit, dass eine Anwendung unbeabsichtigt in einem ungeschützten Zustand betrieben wird. Es ist kein Zufall, dass Vaadin die zweite Funktionsversion zeitgleich mit der Vaadin Enterprise Edition veröffentlicht hat, einer eigenständigen Ausgabe, die sich an Organisationen mit geschäftskritischen Java-Anwendungen richtet. Die gehärteten Voreinstellungen sprechen genau diesen Adressatenkreis an, für den nachweisbare und nicht von individueller Konfiguration abhängige Sicherheit einen unmittelbaren Wert besitzt.
Der Beitrag betrachtet im Folgenden drei laufzeitseitige Neuerungen, die sich in das bestehende Sicherheitsmodell von Vaadin Flow einfügen und die die offizielle Dokumentation an den jeweils systematisch passenden Stellen verortet. Es handelt sich um die Validierung von URL-Schemata, mit der skriptfähige und damit potenziell missbräuchliche Schemata wie javascript: standardseitig zurückgewiesen werden; um die kontrollierte Darstellung nicht vertrauenswürdigen HTML mittels einer jsoup-Safelist, die unmittelbar in die Html-Komponente einzieht; und um den selbsttätig gesendeten Header X-Frame-Options: SAMEORIGIN, der eine missbräuchliche Einbettung der Anwendung in fremde Rahmen unterbindet. Von diesen drei Mechanismen ist eine vierte Maßnahme zu unterscheiden, die der Beitrag gesondert führt: die Härtung der Lieferkette, bei der frisch veröffentlichte npm-Pakete für eine festgelegte Karenzzeit zurückgehalten werden. Sie wirkt nicht zur Laufzeit, sondern auf der Ebene des Build-Werkzeugs, und verdient bereits aus diesem Grund eine eigenständige Einordnung.
Bevor diese Neuerungen im Einzelnen erörtert werden, lohnt ein Blick auf das architektonische Fundament, dem Vaadin Flow seinen Ruf als inhärent sicheres Framework verdankt. Erst vor diesem Hintergrund lässt sich angemessen würdigen, welche Lücken die Neuerungen schließen und an welche bereits etablierten Schutzmechanismen sie anknüpfen.
Das Fundament: Warum Vaadins serverseitige Architektur als inhärent sicher gilt
Bevor die einzelnen Neuerungen erörtert werden, ist das architektonische Fundament zu betrachten, auf dem sie ruhen. Vaadin Flow ist ein serverseitiges Framework: Anwendungszustand, Geschäftslogik und UI-Logik verbleiben vollständig auf dem Server und verlassen ihn zu keinem Zeitpunkt. Anders als bei clientgetriebenen Frameworks legt eine Flow-Anwendung ihre Interna nicht gegenüber dem Browser offen, in dem ein Angreifer Schwachstellen ausnutzen könnte. Die gesamte Kommunikation zwischen Server und Client wickelt das Framework über einen einzigen, abgesicherten Endpunkt ab, der mehrere eingebaute Schutzvorkehrungen trägt. Aus diesem Entwurfsprinzip leitet sich der Ruf von Flow als inhärent sicheres Framework ab — ein Ruf, der jedoch nicht von der Beachtung bewährter Praktiken entbindet.
Wie sich dieses Prinzip im Zusammenspiel von Client und Server konkret auswirkt, lässt sich an einem typischen Vorgang nachvollziehen, etwa der Bearbeitung der eigenen Stammdaten durch einen angemeldeten Benutzer (siehe Diagramm 02-01). Betätigt der Benutzer eine Schaltfläche, so fängt das clientseitige JavaScript des Frameworks dieses Ereignis ab und übersetzt es in wenige, genau umrissene Angaben: die zuvor vom Framework vergebene Kennung der betreffenden Komponente sowie die Art der Interaktion samt zugehöriger Ereignisdetails. Diese Angaben gelangen über den einzigen Server-Endpunkt zum Server, der mithilfe der Standardmechanismen der Servlet-Sitzung die zugehörige Benutzersitzung ermittelt. Vaadin prüft daraufhin die Sitzungsangaben und vergewissert sich, dass eine Komponente mit der übermittelten Kennung tatsächlich existiert, bevor die serverseitige Behandlungsroutine ausgeführt wird. Lädt diese Routine vertrauliche Daten aus der Datenhaltung, so entscheidet allein der Entwickler, welche Teile davon zur Anzeige gelangen; alles Übrige verbleibt auf dem Server und wird nach Abschluss der Verarbeitung verworfen. An den Client wird ausschließlich der zur Darstellung bestimmte Wert gesendet — der Browser erfährt nicht einmal, dass auf dem Server ein vollständiges Benutzerobjekt existiert.

Einziger gesicherter Endpunkt und serverseitiger Zustand
In einer Flow-Anwendung entstehen keine öffentlichen Web Services. Sämtliche Kommunikation läuft über einen einzigen HTTP-Anforderungsbehandler, der entfernte Prozeduraufrufe über die Standard-Servlet-Schnittstelle abwickelt. Da die Geschäftslogik somit nicht als Web Service nach außen geöffnet wird, verringert sich die Zahl der Eintrittspunkte, die einem Angreifer offenstehen.
Darüber hinaus ist sich der Server jederzeit des Anwendungszustands bewusst und kennt insbesondere, was auf dem Bildschirm des Benutzers gegenwärtig sichtbar ist. Daraus folgt eine wirksame Schutzeigenschaft: Vaadin verweigert jede Interaktion mit Komponenten, die nicht sichtbar sind oder serverseitig deaktiviert wurden. Setzt der Entwickler eine Komponente auf deaktiviert, so wirkt dies sowohl auf dem Server als auch im Client. Im Browser kann ein Angreifer diese Deaktivierung zwar aufheben, da er die volle Kontrolle über alles besitzt, was im Browser geschieht; der Server jedoch blockiert jede Interaktion mit der Komponente und vermerkt einen entsprechenden Warnhinweis im Serverprotokoll.
Button button = new Button("Click me for effect!");
button.setEnabled(false);
button.addClickListener(event -> {
// If the button is disabled, this listener does not run,
// even if an attacker re-enables the button on the client side.
});
Ergänzend empfiehlt Vaadin, die Kommunikation ausnahmslos über HTTPS zu führen. Flow arbeitet ohne zusätzlichen Konfigurationsaufwand im Anwendungscode mit HTTPS zusammen; die Einrichtung des gesicherten Endpunkts obliegt dem jeweiligen Servlet-Container.
Serverseitige Validierung, die clientseitig nicht umgangen werden kann
Die Datenbindungs-API von Vaadin unterstützt eine Validierung auf dem Server, die sich durch clientseitige Angriffe nicht umgehen lässt. Zwar bieten die Komponenten auch eine clientseitige Prüfung an, die die Reaktionsfreude der Anwendung erhöht; diese dient jedoch ausschließlich dem Bedienkomfort und kann im Browser ausgehebelt werden. Wie bei jeder Webanwendung gilt daher der Grundsatz, dass sämtliche vom Client stammenden Daten erneut zu prüfen sind, sobald sie den Server erreichen. Auf die clientseitige Validierung allein zu vertrauen, wäre nicht sicher. Vaadin stellt zu diesem Zweck vorgefertigte serverseitige Prüfer bereit; darüber hinaus steht es dem Entwickler frei, jede beliebige Java-Schnittstelle zur Prüfung heranzuziehen, bis hin zur Anbindung externer Dienste.
TextField nameField = new TextField("Name");
Binder<Account> binder = new Binder<>(Account.class);
binder.forField(nameField)
// This validator runs on the server and cannot be
// bypassed from the browser.
.withValidator(name -> name != null && !name.isBlank(),
"The name must not be empty")
.bind(Account::getName, Account::setName);
Ein verwandter Gesichtspunkt betrifft Daten, die aus einer Datenhaltung stammen und als HTML in DOM-Elemente eingefügt werden. Solche Daten sind zu maskieren; die hierfür einschlägigen Mechanismen behandelt das Kapitel zum Cross-Site-Scripting, in das sich die erste der drei Neuerungen unmittelbar einfügt. Damit ist der Maßstab benannt, an dem sich die folgenden Neuerungen messen lassen.
Schutz vor unsicheren URL-Schemata (neu in 25.2)
Mit der ersten der drei laufzeitseitigen Neuerungen wendet sich der Beitrag einem Bereich zu, den die offizielle Dokumentation im Abschnitt zu den verbreiteten Schwachstellen verortet und der unmittelbar an das im vorigen Kapitel benannte Schutzmodell gegen Cross-Site-Scripting anknüpft. Es geht um die Validierung der Schemata, mit denen URLs in der Anwendung gesetzt werden. Was bislang in der Verantwortung des Entwicklers lag, übernimmt Vaadin in Version 25.2 standardseitig: Übergebene URLs werden gegen eine Liste sicherer Schemata geprüft, und ein unsicheres Schema führt zu einem sofortigen Abbruch.
Der Angriffsvektor javascript: und die neue Validierung
URLs, die ein skriptfähiges Schema verwenden, bilden einen verbreiteten Vektor für Cross-Site-Scripting. Das prominenteste Beispiel ist das Schema javascript:, das anstelle eines Verweisziels ausführbaren Code transportiert; wird eine solche URL als Ziel eines Verweises oder als Quelle eines eingebetteten Rahmens gesetzt, so kann sie bei der Auslösung im Browser beliebigen Code zur Ausführung bringen. Eben hier setzt die Neuerung an. Die Methoden Anchor.setHref, IFrame.setSrc und Page.open prüfen die ihnen übergebene URL nunmehr gegen eine Liste als sicher eingestufter Schemata. Erweist sich das Schema als unsicher, lösen sie eine IllegalArgumentException aus und verhindern damit, dass die problematische URL überhaupt in das Dokument gelangt.
Anchor anchor = new Anchor();
// A script-capable scheme such as "javascript:" is now rejected:
// this call throws an IllegalArgumentException instead of
// inserting the dangerous URL into the document.
anchor.setHref("javascript:alert('XSS')");
Der Vorteil dieses Verhaltens liegt darin, dass die Prüfung nicht erst zur Laufzeit im Browser, sondern bereits beim Setzen der URL auf dem Server greift. Eine unsichere URL erreicht den Client somit gar nicht erst.
Zugelassene Schemata, Konfiguration und bewusste Ausnahmen
Standardmäßig gelten die Schemata http, https, mailto, tel und ftp als sicher. Relative URLs besitzen kein Schema und werden stets akzeptiert, sodass die anwendungsinterne Navigation von der Prüfung unberührt bleibt. Der Kreis der zugelassenen Schemata lässt sich über den Konfigurationsparameter com.vaadin.safeUrlSchemes anpassen, der eine durch Kommata getrennte Liste entgegennimmt. Der Parameter kann je nach Betriebsumgebung etwa als System-Eigenschaft oder als Kontextparameter der Anwendung gesetzt werden.
# Comma-separated list of schemes considered safe.
# An entry of "*" marks every scheme as safe and thereby
# disables the validation entirely.
com.vaadin.safeUrlSchemes = http,https,mailto,tel,ftp
Ein einzelner Eintrag * markiert jedes Schema als sicher und deaktiviert die Prüfung vollständig; von dieser Möglichkeit sollte nur mit Bedacht Gebrauch gemacht werden, da sie den gewonnenen Schutz wieder aufhebt. Für den umgekehrten Fall, dass eine einzelne URL nachweislich der eigenen Kontrolle unterliegt und als sicher gelten darf, stehen dedizierte Setter bereit, welche die Prüfung gezielt umgehen: Anchor.setUnsafeHref, IFrame.setUnsafeSrc und Page.openUnsafe. Sie sind als bewusst zu wählende Ausnahme gedacht und tragen die Unsicherheit bereits im Namen.
Anchor anchor = new Anchor();
// Use only for URLs that are fully under your control and
// known to be safe; this deliberately bypasses the validation.
anchor.setUnsafeHref(trustedUrl);
Einordnung in das bestehende XSS-Schutzmodell
Die Maßnahme fügt sich nahtlos in das übergreifende Schutzmodell gegen Cross-Site-Scripting ein, das Vaadin bereits zuvor kennzeichnete. Inhalte werden standardmäßig als Text und nicht als HTML gerendert, und die Zulassung ausführbarer Inhalte bleibt stets eine ausdrückliche Entwicklerentscheidung. Die Schema-Validierung schließt nun eine Lücke, die innerhalb dieses Modells bislang offenstand: Während die textuelle Behandlung von Inhalten das Einschleusen von Markup verhinderte, blieb das Setzen einer skriptfähigen URL als eigenständiger Weg bestehen. Indem Vaadin diesen Weg standardseitig versperrt und zugleich eine bewusste Ausnahme vorsieht, folgt die Neuerung demselben Leitgedanken sicherer Voreinstellungen, der den gesamten Beitrag durchzieht: Der Schutz greift ohne Zutun des Entwicklers, und seine Aufhebung verlangt eine ausdrückliche, im Quelltext sichtbare Entscheidung.
Sichere Darstellung nicht vertrauenswürdigen HTML mit Safelist (neu in 25.2)
Die zweite laufzeitseitige Neuerung verbleibt im Umfeld des XSS-Schutzes und greift einen Faden auf, der bereits im vorigen Kapitel angedeutet wurde: den Umgang mit Daten, die als HTML in das Dokument gelangen sollen. Wo das Setzen einer skriptfähigen URL durch die Schema-Validierung versperrt wird, bleibt die bewusste Darstellung von HTML-Inhalten ein eigenständiger Vorgang, der bislang eine eigenhändige Bereinigung verlangte. Vaadin 25.2 verlagert diese Bereinigung in die Komponente selbst und nimmt dem Entwickler damit eine fehleranfällige Pflicht ab.
Vom Standardverhalten „Text statt HTML“ zur expliziten HTML-Zulassung
Vaadin rendert Inhalte standardmäßig als Text und nicht als HTML, indem es auf Browser-Schnittstellen zurückgreift, die den übergebenen Wert als reinen Text behandeln. Auf diese Weise lässt sich kein Markup einschleusen; ein als Text gesetzter Wert wird selbst dann nicht interpretiert, wenn er Auszeichnungen wie <script>-Elemente enthält. Einige Komponenten erlauben für bestimmte Inhalte gleichwohl ausdrücklich HTML. Die Zulassung unsicherer HTML-Inhalte ist dabei niemals die Voreinstellung, sondern stets eine bewusste Entscheidung des Entwicklers. Wer sie traf, musste die Inhalte bislang selbst bereinigen, etwa mithilfe von jsoup und einer passenden Safelist.
Div div = new Div();
// Rendered as plain text — the markup is not interpreted:
div.setText("<b>This will not be bold.</b>");
// The previous approach: sanitise the content manually before
// adding it as HTML.
String safeHtml = Jsoup.clean(dangerousText, Safelist.relaxed());
div.add(new Html(safeHtml));
Dieses Vorgehen war wirksam, verlagerte die Verantwortung jedoch vollständig auf den Entwickler. Wurde der Bereinigungsschritt vergessen oder an einer von mehreren Stellen ausgelassen, an denen Inhalt zugewiesen wird, so öffnete sich erneut ein Einfallstor.
Die neuen Html-Konstruktoren und die Serialisierungsfalle
Seit Version 25.2 kann die Html-Komponente die Bereinigung selbst übernehmen. Die neuen Konstruktoren nehmen einen Lieferanten für eine jsoup-Safelist entgegen und bereinigen damit sowohl den anfänglichen als auch jeden später zugewiesenen Inhalt. Die Verantwortung für die Bereinigung wandert damit von der Aufrufstelle in die Komponente, die sie zuverlässig bei jeder Inhaltszuweisung anwendet.
Div div = new Div();
// Since 25.2: the Html component sanitises the content itself,
// for both the initial content and any content assigned later.
div.add(new Html(dangerousText, Safelist::relaxed));
Ein wichtiger Fallstrick verdient besondere Beachtung. Zu übergeben ist ein Lieferant in Gestalt einer Methodenreferenz oder eines Lambda-Ausdrucks, der die Safelist jeweils neu erzeugt — nicht jedoch eine vorab erzeugte Safelist-Instanz, die der Lieferant lediglich erfasst. Der Grund liegt in der Serialisierung: Eine jsoup-Safelist ist nicht serialisierbar, und das Erfassen einer solchen Instanz innerhalb des Lieferanten würde die Serialisierung der Sitzung beschädigen. Die Übergabe einer Methodenreferenz wie Safelist::relaxed umgeht dieses Problem, da nicht die Instanz, sondern die Vorschrift zu ihrer Erzeugung gespeichert wird. Schließlich ist zu beachten, dass die Erlaubnisliste das Wurzelelement des darzustellenden Fragments zulassen muss; andernfalls würde gerade jenes Element entfernt, das den Inhalt trägt.
Clickjacking-Schutz durch X-Frame-Options: SAMEORIGIN (neu in 25.2)
Die dritte laufzeitseitige Neuerung führt die offizielle Dokumentation im Abschnitt zu den häufig gemeldeten Problemen, da das Fehlen des einschlägigen Headers in der Vergangenheit wiederholt beanstandet wurde. Mit Version 25.2 sendet Vaadin diesen Header nun selbsttätig und begegnet damit standardseitig einer Angriffsform, die auf die Täuschung des Benutzers zielt.
Bedrohungsmodell und neues Standardverhalten
Beim Clickjacking wird eine Anwendung in einen fremden Rahmen eingebettet und so mit einer manipulierten Oberfläche überlagert, dass der Benutzer unwissentlich Aktionen innerhalb der eingebetteten Anwendung auslöst, während er eine harmlose Schaltfläche der umgebenden Seite zu betätigen meint. Der Header X-Frame-Options ist das etablierte Mittel, mit dem eine Webseite dem Browser mitteilt, dass sie nicht innerhalb eines Rahmens einer anderen Seite ausgeführt werden soll. Auf diese Weise lässt sich verhindern, dass die Anwendung in eine bösartige Seite eingebettet wird, in der ein Angreifer Benutzeraktionen abfängt (siehe Bild 05-01).

Vaadin sendet diesen Header in Version 25.2 standardmäßig mit der Anwendungsseite und versieht ihn mit dem Wert SAMEORIGIN. Dadurch kann die Anwendung ausschließlich in Rahmen desselben Ursprungs dargestellt werden, während die Einbettung durch fremde Ursprünge unterbunden wird.
X-Frame-Options: SAMEORIGIN
Der Parameter frameOptions und die Wechselwirkung mit Spring Security
Das Standardverhalten lässt sich über den Konfigurationsparameter frameOptions anpassen. Wird der Wert auf DENY gesetzt, so verbietet er die Einbettung in Rahmen vollständig, also auch durch denselben Ursprung. Soll eine Anwendung hingegen absichtlich in einen Rahmen eines fremden Ursprungs eingebettet werden, so ist der Parameter auf einen leeren Wert zu setzen, wodurch der Header entfällt.
# Send "X-Frame-Options: DENY" to forbid framing entirely,
# or leave the value empty to omit the header for an application
# that is intentionally embedded in a cross-origin frame.
com.vaadin.frameOptions = DENY
Von besonderer Bedeutung ist eine Wechselwirkung, die zugleich die wichtigste Verhaltensänderung beim Upgrade darstellt: Vaadin setzt den Header nur dann, wenn die Antwort nicht bereits einen X-Frame-Options-Header enthält. Ein Header, der durch einen anderen Mechanismus gesetzt wurde — etwa durch Spring Security oder einen Servlet-Filter —, wird nicht überschrieben. Wer den Schutz bislang über einen solchen Mechanismus bereitstellte, behält somit das gewohnte Verhalten; in allen übrigen Fällen greift der neue Standard, ohne dass eine bestehende, abweichend gesetzte Konfiguration verdrängt würde. Gerade weil diese Änderung das Verhalten ausgelieferter Anwendungen unmittelbar berührt, verdient sie beim Wechsel auf Version 25.2 besondere Aufmerksamkeit.
Härtung der Lieferkette gegen kompromittierte npm-Pakete (neu in 25.2)
Mit der vierten sicherheitsrelevanten Neuerung verlässt der Beitrag bewusst das laufzeitseitige Flow-Sicherheitsmodell. Sie liegt nicht im Bereich der Anwendung, die zur Laufzeit Anfragen verarbeitet, sondern auf der Ebene des Build-Werkzeugs, das die Anwendung erst zusammenstellt. Diese Verschiebung der Wirkebene ist der Grund, weshalb die Maßnahme hier gesondert geführt wird; der Leser sollte sie nicht mit den drei zuvor behandelten Laufzeitmechanismen verwechseln.
Den Hintergrund bildet ein Risiko, das jede Anwendung betrifft, deren Build auf Pakete aus einer öffentlichen Registratur zurückgreift. Wird ein solches Paket kompromittiert — etwa indem ein Angreifer die Zugangsdaten eines Betreuers erlangt und eine bösartige Fassung veröffentlicht —, so kann der schädliche Code in den Build und damit in die ausgelieferte Anwendung gelangen, bevor die Gemeinschaft den Vorfall überhaupt bemerkt. Solche Angriffe auf die Lieferkette entfalten ihre Wirkung gerade in dem kurzen Zeitfenster unmittelbar nach der Veröffentlichung, in dem die kompromittierte Fassung bereits verfügbar, der Missbrauch aber noch nicht erkannt ist.
Vaadin 25.2 begegnet diesem Risiko mit einer einfachen, aber wirksamen Voreinstellung: Standardmäßig installiert das Framework keine npm-Pakete, die innerhalb des letzten Tages veröffentlicht wurden (siehe Diagramm 06-01). Damit wird dem Ökosystem die Zeit eingeräumt, eine kompromittierte Veröffentlichung zu erkennen und zurückzuziehen, bevor sie den Build erreicht. Die Maßnahme verlagert das Eintreffen frisch veröffentlichter Pakete also bewusst um eine Karenzzeit nach hinten und entzieht dem Angreifer eben jenes kurze Zeitfenster, auf das er angewiesen ist.

Die Länge dieser Karenzzeit ist konfigurierbar und lässt sich je nach Bedarf verkürzen oder verlängern; den genauen Konfigurationsweg führt die offizielle Dokumentation aus. Als Voreinstellung des Build-Werkzeugs reiht sich die Maßnahme in das übergreifende Leitmotiv des Beitrags ein, ändert dieses jedoch um eine wesentliche Facette: Während die drei laufzeitseitigen Neuerungen die Anwendung im Betrieb absichern, schützt die Lieferketten-Härtung bereits den Entstehungsprozess der Anwendung. Beide Ebenen ergänzen einander, wirken aber an unterschiedlichen Stellen — eine Unterscheidung, die für das Verständnis der gesamten Neuerungen von Belang ist.
Der bestehende Schutzschild im Überblick, in den sich die Neuerungen einfügen
Die bisher behandelten Neuerungen entfalten ihre Wirkung nicht im luftleeren Raum, sondern fügen sich in ein Geflecht etablierter Schutzmechanismen ein, die Vaadin bereits ohne Zutun des Entwicklers bereitstellt. Erst dieser Überblick macht sichtbar, an welche bestehenden Vorkehrungen die Neuerungen anknüpfen und welche Lücken sie schließen. Drei Bereiche verdienen besondere Beachtung: der automatische Schutz vor Cross-Site-Request-Forgery, die Zugriffssteuerung der Navigation und schließlich jene Felder, in denen die Verantwortung bewusst beim Entwickler verbleibt.
Automatischer CSRF-Schutz nach dem Synchronizer-Token-Muster
Sämtliche Anfragen zwischen Client und Server tragen ein sitzungsspezifisches Token zum Schutz vor Cross-Site-Request-Forgery. Da Vaadin die gesamte Kommunikation selbst abwickelt, muss der Entwickler diese Token weder manuell einfügen noch überprüfen; der Schutz greift ohne sein Zutun. Vaadin folgt dabei dem Synchronizer-Token-Muster, einem etablierten Entwurfsmuster zur Abwehr gefälschter Anfragen.
Zwei Token kommen zum Einsatz. Anfragen des UIDL-Protokolls sind durch das Token Vaadin-Security-Key geschützt, Verbindungen über WebSocket durch das Token Vaadin-Push-ID. Der Sicherheitsschlüssel wird je UI-Instanz erzeugt und als Teil der Bootstrap-Antwort genau einmal pro geöffnetem Browser-Tab an den Client gesendet; bei einer Aktualisierung der Seite oder beim Öffnen eines neuen Tabs erzeugt Vaadin einen neuen Schlüssel. Die Push-Kennung wird je Benutzersitzung erzeugt und dient der Prüfung, ob die in der WebSocket-Anfrage übermittelte Kennung mit jener der Sitzung übereinstimmt. Fehlt sie, so wird die Anfrage verworfen und die WebSocket-Verbindung geschlossen.
{
"appConfig": {
"uidl": {
"Vaadin-Security-Key": "f0ef03d7-0cf4-4f32-834d-47b88a1034b7",
"Vaadin-Push-ID": "ad3744de-2280-4531-84df-6a70a5fe958e"
}
}
}
Zugriffssteuerung der Navigation mit annotations- und pfadbasierten Prüfern
Die Navigationszugriffssteuerung tritt als Nachfolgemodell des früheren ViewAccessChecker an die Stelle einer einzelnen Prüfinstanz und erweitert deren Möglichkeiten erheblich. Sie fängt Navigationsereignisse ab, wertet die konfigurierten Regeln aus und entscheidet daraufhin, ob die Zielansicht dargestellt oder der Zugriff verweigert wird. Ihr Aufbau ruht auf drei Bausteinen (siehe Diagramm 07-01): NavigationAccessControl bildet den Eintrittspunkt, der die Navigationsereignisse beobachtet und die Regeln auswertet; die Prüfer in Gestalt von Implementierungen der Schnittstelle NavigationAccessChecker verkörpern die einzelnen Sicherheitsregeln; und der AccessCheckDecisionResolver fällt aus deren Ergebnissen die endgültige Entscheidung.

Vaadin liefert zwei Prüfer mit. Der annotationsbasierte Prüfer wertet die Sicherheitsannotationen @AnonymousAllowed, @PermitAll, @RolesAllowed und @DenyAll an der Zielansicht aus; dabei stammt @AnonymousAllowed aus Vaadin selbst, während die rollenbezogenen Annotationen aus der standardisierten Annotationsschnittstelle herrühren, die Vaadin erkennt. Der pfadbasierte Prüfer wertet hingegen den zur Navigation verwendeten Pfad aus und stützt sich in Spring-Projekten auf eine Implementierung, die an die Spring-Security-Konfiguration anknüpft.
@Route("admin")
@RolesAllowed("ADMIN")
public class AdminView extends Div {
// Only users with the ADMIN role may navigate here.
}
@Route("")
@AnonymousAllowed
public class PublicView extends Div {
// Open to everyone, including unauthenticated users.
}
Stehen mehrere Prüfer in Verwendung, so trägt jeder ein Ergebnis bei, und der Entscheidungsauflöser leitet daraus die endgültige Entscheidung ab. Die Standardimplementierung folgt im regulären Navigationsfall den nachstehenden Regeln; jedes einzelne Ergebnis kann ALLOW, DENY, NEUTRAL oder REJECT lauten, wobei NEUTRAL eine Enthaltung mangels hinreichender Information bezeichnet und REJECT einen kritischen Konfigurationsfehler signalisiert.
| Ergebnisse der Prüfer | Entscheidung |
|---|---|
ausschließlich ALLOW | ALLOW |
ALLOW und NEUTRAL | ALLOW |
ausschließlich DENY | DENY |
DENY und NEUTRAL | DENY |
ausschließlich NEUTRAL | DENY |
ALLOW und DENY | REJECT |
ALLOW, DENY und NEUTRAL | REJECT |
Widersprechen sich die Prüfer — verbleibende Enthaltungen ausgenommen —, so weist der Standardauflöser die Navigation also zurück, was im Entwicklungsmodus eine Ausnahme auslöst und so auf eine fehlerhafte Sicherheitskonfiguration aufmerksam macht. In der Fehlerbehandlungsphase gilt nahezu dieselbe Regel, mit der einen Abweichung, dass durchgängige Enthaltung dort den Zugriff gewährt, da das Navigationsziel in diesem Fall eine Fehlerbehandlungsansicht ohne schützenswerte Informationen ist.
Reichen die mitgelieferten Prüfer nicht aus, lässt sich ein eigener NavigationAccessChecker umsetzen, dessen einzige Methode einen NavigationContext entgegennimmt und ein AccessCheckResult zurückgibt. In reinen Java-Projekten besteht keine sofort einsatzbereite Pfadprüfung; das gewünschte Verhalten wird stattdessen über einen VaadinServiceInitListener eingerichtet, der die NavigationAccessControl mit den gewünschten Prüfern und dem Auflöser erzeugt. Für Ihre Ausrichtung ohne Spring ist somit vor allem der annotationsbasierte Prüfer von Belang, der ohne weitere Rahmenwerke auskommt.
Eigenverantwortung bei SQL-Injektion, executeJs und Templates
In drei Feldern verbleibt die Verantwortung bewusst beim Entwickler, da Vaadin als oberflächenseitiges Framework hier keine allgemeingültige Schutzmaßnahme treffen kann. Das erste betrifft SQL-Injektionen. Vaadin greift nicht unmittelbar auf die Datenhaltung zu, sodass deren Absicherung dem gewählten Backend und dem Entwickler obliegt. Werden vorbereitete Anweisungen verwendet, lässt sich diese Angriffsform jedoch vollständig ausschließen, da der übergebene Wert die Struktur der Abfrage nicht mehr verändern kann.
// Use a prepared statement so the user-supplied value cannot
// alter the structure of the SQL query.
String sql = "UPDATE app_users SET name = ? WHERE id = ?";
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, value);
statement.setLong(2, user.getId());
statement.executeUpdate();
}
Das zweite Feld betrifft die Ausführung eigenen JavaScripts über executeJs. Das Ausführen beliebigen Skripts ist von Natur aus unsicher, da das Skript vollen Zugriff auf die gesamte Clientseite besitzt; besonders heikel ist es, wenn das Skript nicht im Anwendungscode steht, sondern dynamisch geladen wird. Parameter lassen sich jedoch sicher übergeben, indem sie als Argumente und nicht durch Verkettung in die Skriptzeichenkette gelangen.
// Parameters are passed safely as arguments; never concatenate
// untrusted values into the script string itself.
String script = "window.alert($0)";
UI.getCurrent().getPage().executeJs(script, untrustedValue);
Das dritte Feld betrifft Templates. Beim Einsatz von Templates ist beim Einfügen von Daten in das DOM sowie bei der Verwendung von JavaScript erhöhte Sorgfalt geboten. Vaadin behandelt Zeichenkettenwerte aus dem serverseitigen Modell sicher, hat jedoch keine Kontrolle über das, was im Template selbst mit HTML oder JavaScript geschieht. Wie schon bei der serverseitigen Validierung gilt der Grundsatz, dass vom Client stammenden Werten niemals zu vertrauen ist.
Sichere Konfiguration und Betrieb
Das Sicherheitsniveau einer ausgelieferten Anwendung entscheidet sich nicht allein an den Mechanismen des Frameworks, sondern auch an betrieblichen Aspekten, die in der Verantwortung des Betreibers liegen. Zwei davon greift dieses Kapitel heraus: die Speicherung sensibler Daten außerhalb der Projektdateien sowie die Möglichkeiten und Grenzen einer strikten Content Security Policy.
Speicherung sensibler Daten außerhalb der Projektdateien
Es gilt als schlechte Praxis, sensible Angaben wie die Adresse, den Benutzernamen oder das Passwort einer Datenbank in der Konfigurationsdatei der Anwendung abzulegen. Gelangen solche Geheimnisse in Projektdateien oder Quelltext, so drohen sie zu entweichen, sobald das Projekt in eine Versionsverwaltung übernommen wird. Der Grundsatz lautet daher: Passwörter und andere Geheimnisse dürfen unter keinen Umständen in das Versionsverwaltungssystem eingespielt werden.
Die Dokumentation beschreibt drei Wege, sensible Daten auszulagern. Für eine Ausrichtung ohne zusätzliche Abhängigkeiten ist der erste der unmittelbar tragfähige: das Setzen der Werte als Systemumgebungsvariablen. Sie sind dem Betriebssystem zu eigen, verlangen keine Bibliothek und lassen sich aus reinem Java auslesen.
// Read secrets from the environment rather than from project files.
String dbUrl = System.getenv("DB_URL");
String dbUser = System.getenv("DB_USER");
String dbPassword = System.getenv("DB_PASSWORD");
Auf Unix-artigen Systemen werden die Variablen etwa wie folgt gesetzt; für eine dauerhafte Geltung sind die Befehle in die Profildatei der Anmelde-Shell aufzunehmen.
export DB_URL=jdbc:postgresql://localhost:5432/postgres
export DB_USER=postgres
export DB_PASSWORD=secret
Die beiden weiteren dokumentierten Wege sind an Spring Boot gebunden und daher für eine Spring-freie Anwendung nachrangig. Der eine importiert eine externe Eigenschaftsdatei über die Spring-Eigenschaft spring.config.import, sodass die Geheimnisse außerhalb des Projektverzeichnisses verbleiben. Der andere legt die Werte mittels Jasypt verschlüsselt ab: Die als DEC(...) markierten Klartextwerte werden an Ort und Stelle zu ENC(...) verschlüsselt und dürfen sodann in die Versionsverwaltung gelangen, da sie nur noch in verschlüsselter Form vorliegen. Entscheidend ist hierbei der Hinweis, dass das zur Verschlüsselung verwendete Hauptpasswort selbst niemals in die Versionsverwaltung gelangen darf; gerät es in fremde Hände, so sind sämtliche verschlüsselten Werte kompromittiert, sobald der Angreifer zugleich Lesezugriff auf die verschlüsselte Datei besitzt.
Strikte Content Security Policy: Möglichkeiten und Grenzen
Die Content Security Policy ist ein Browserstandard, der bestimmten Angriffsarten begegnet, indem die Anwendung Regeln dafür festlegt, wie JavaScript geladen und ausgeführt werden darf. Eine strikte Ausprägung bietet eine zusätzliche Schutzschicht gegen Cross-Site-Scripting, das Einschleusen von Daten, Datendiebstahl, die Verunstaltung der Seite und die Verbreitung von Schadsoftware.
Vaadin Flow ist mit strikten CSP-Regeln allerdings grundsätzlich nicht ohne Weiteres vereinbar. Der Grund liegt in der Art der Client-Server-Kommunikation, die sich dynamisch erzeugter JavaScript-Funktionen und von eval-Aufrufen bedient — beides ist mit strikten CSP-Regeln unvereinbar. Mit einigem Aufwand lässt sich gleichwohl eine nonce-basierte strikte CSP einsetzen. Eine Nonce ist eine vom Server für jede HTTP-Anfrage neu erzeugte Zufallskennung, die in die Antwort-Header aufgenommen wird; ist dies geschehen, lädt der Browser nur noch Skript-Elemente, die diese Nonce tragen, während dynamische Funktionen und eval-Aufrufe untersagt bleiben. Dieses Vorgehen steht ausschließlich im Produktionsmodus zur Verfügung.
Die Erzeugung der Nonce übernimmt ein IndexHtmlRequestListener, der die Kennung erzeugt und sowohl der Antwort der Index-Datei als auch allen Skript-Elementen hinzufügt.
String nonce = UUID.randomUUID().toString();
// Require the nonce on every script tag via the CSP header.
response.getVaadinResponse().setHeader("Content-Security-Policy",
"script-src 'nonce-" + nonce + "'");
// Add the nonce to all script tags in the host page.
response.getDocument().getElementsByTag("script").attr("nonce", nonce);
Damit allein ist es jedoch nicht getan. Um die dynamischen Funktionen, die eval-Aufrufe und das nachladende Laden von Code regelkonform zu behandeln, muss die Anwendung eine eigene JavaScript-Datei bereitstellen, die den Function-Konstruktor und eval überschreibt und für sämtliche von der Anwendung getätigten Aufrufe gleichwertige, vorab definierte Funktionen anbietet. Dieser Aufwand ist erheblich und in der Praxis nur dann zu rechtfertigen, wenn eine strikte CSP zwingend gefordert ist. Das Kapitel macht somit beides deutlich: dass eine strikte CSP mit Vaadin Flow erreichbar ist, und dass sie ihren Preis hat.
Migrationshinweise: sicherheitsbedingte Verhaltensänderungen beim Upgrade auf 25.2
Da es sich bei 25.2 um eine Funktionsversion innerhalb der 25.x-Linie handelt, übernehmen die meisten Anwendungen den Versionssprung ohne Schwierigkeiten. Gleichwohl bringt die Härtung der Voreinstellungen einige sicherheitsbedingte Verhaltensänderungen mit sich, die vor der Anhebung der Version bedacht sein wollen. Sie sind keine Mängel des Upgrades, sondern die unmittelbare Folge des Grundsatzes sicherer Voreinstellungen: Wo ein Schutz nun ohne Zutun greift, ändert sich das Verhalten gegenüber einem Zustand, in dem er zuvor fehlte.
Drei Änderungen verdienen Beachtung. Erstens sendet Vaadin den Header X-Frame-Options: SAMEORIGIN nunmehr standardmäßig; Anwendungen, die bewusst in einen Rahmen eines fremden Ursprungs eingebettet werden, müssen dies durch Leeren des Parameters ausdrücklich erlauben. Zweitens validiert Vaadin die Schemata gesetzter URLs; dies wird für Teams relevant, die zuvor unkonventionelle Schemata verwendeten, und verlangt dort entweder eine Anpassung der zugelassenen Schemata oder den Rückgriff auf die bewussten Ausnahme-Setter. Drittens hält das Build-Werkzeug frisch veröffentlichte npm-Pakete für eine Karenzzeit zurück, was sich auf Builds auswirken kann, die auf erst kürzlich veröffentlichte Pakete angewiesen sind.
Die folgende Übersicht fasst die geänderten Voreinstellungen und die jeweils vorgesehenen Ausnahme- beziehungsweise Anpassungsmechanismen zusammen.
| Neuerung | Neues Standardverhalten | Ausnahme- bzw. Anpassungsmechanismus |
|---|---|---|
| URL-Schema-Validierung | Unsichere Schemata wie javascript: werden zurückgewiesen | Zugelassene Schemata über com.vaadin.safeUrlSchemes anpassen; einzelne URLs über setUnsafeHref, setUnsafeSrc oder openUnsafe ausnehmen |
| Clickjacking-Schutz | X-Frame-Options: SAMEORIGIN wird gesendet | Über frameOptions auf DENY verschärfen oder zur Fremdeinbettung leeren; ein bereits anderweitig gesetzter Header bleibt unberührt |
| Lieferketten-Härtung | npm-Pakete jünger als ein Tag werden nicht installiert | Karenzzeit nach Bedarf verkürzen oder verlängern |
Eine vierte Neuerung, die bereinigende Darstellung nicht vertrauenswürdigen HTML über die neuen Html-Konstruktoren, ist hier nicht aufgeführt, da sie additiv wirkt: Sie ergänzt eine neue, ausdrücklich zu wählende Möglichkeit, ohne bestehendes Verhalten zu verändern, und stellt somit kein Migrationsrisiko dar.
Vor der Anhebung der Version empfiehlt sich in jedem Fall die Sichtung der offiziellen Versionshinweise und des Upgrade-Leitfadens, der die einzelnen Änderungen — auch jene jenseits des Sicherheitsbereichs, etwa an der Signals-API und an der Build- und Werkzeugkonfiguration — im Einzelnen durchgeht. Wer die hier genannten Verhaltensänderungen kennt und die vorgesehenen Ausnahmemechanismen gezielt einsetzt, wird das Upgrade in aller Regel reibungslos vollziehen.
Fazit: Sicherheit als Voreinstellung und ihre Bedeutung für Compliance-Anforderungen
Am Ende der Betrachtung fügen sich die einzelnen Neuerungen zu einem stimmigen Bild. Vaadin 25.2 ergänzt das ohnehin tragfähige serverseitige Sicherheitsmodell von Flow, dessen inhärente Stärke im einleitenden Architekturkapitel umrissen wurde, um drei wirksame laufzeitseitige Voreinstellungen — die Validierung unsicherer URL-Schemata, die bereinigende Darstellung nicht vertrauenswürdigen HTML über die neuen Html-Konstruktoren und den selbsttätig gesendeten Clickjacking-Schutz — und erweitert es überdies um eine Maßnahme, die bereits die Lieferkette absichert. Keine dieser Neuerungen erfindet das Sicherheitsmodell neu; jede schließt vielmehr eine bislang offene Lücke an genau der Stelle, an der das bestehende Modell sie vorsah.
Der eigentliche Gewinn liegt weniger in den einzelnen Mechanismen als in dem Prinzip, das sie verbindet. Sichere Voreinstellungen verringern jene Fehlerquelle, die in der Praxis am schwersten wiegt: das Unterlassen einer notwendigen Konfiguration. Wo ein Schutz im Auslieferungszustand bereits greift und seine Aufhebung eine ausdrückliche, im Quelltext sichtbare Entscheidung verlangt, verschiebt sich die Beweislast zugunsten der Sicherheit. Eine Anwendung ist dann nicht mehr deshalb geschützt, weil jemand daran gedacht hat, sondern weil das Gegenteil eines bewussten Eingriffs bedürfte.
Gerade diese Verlässlichkeit ist es, die den Neuerungen über den rein technischen Wert hinaus Bedeutung verleiht. Für Organisationen, die regulatorischen oder unternehmerischen Anforderungen unterliegen, zählt nicht allein, dass ein Schutz möglich ist, sondern dass er nachweisbar und unabhängig von der Sorgfalt einzelner Konfigurationsentscheidungen wirkt. Es ist daher folgerichtig, dass Vaadin die gehärteten Voreinstellungen demselben Adressatenkreis zuordnet, an den sich die zeitgleich erschienene Enterprise Edition richtet: Beide zielen auf einen Betrieb, in dem Sicherheit nicht als nachträgliche Zutat, sondern als Eigenschaft des Fundaments verstanden wird.
Wer 25.2 in Betrieb nimmt, sollte die wenigen sicherheitsbedingten Verhaltensänderungen kennen und die vorgesehenen Ausnahmemechanismen gezielt einsetzen; im Übrigen darf er sich darauf verlassen, dass die Voreinstellungen ihre Arbeit verrichten, ohne dass er sie eigens anstoßen müsste. Für die vertiefte Auseinandersetzung mit den einzelnen Mechanismen und ihrer Konfiguration bleibt die offizielle Sicherheitsdokumentation die maßgebliche Quelle.