#JAVAPRO #JAVAEE8 #Security #JSR375
Die Java-EE -Security-API in Java EE 8 ist die wesentlichste Neuerung zum Thema Sicherheit für die Java Enterprise Plattform seit Langem. Damit ist Java EE den Anforderungen von Cloud-Anwendungen besser gewachsen. Der JSR 375 (Java Specification Request) beschreibt die Funktionen für portable Nutzerverwaltung, Authentifizierung, Autorisierung, Rollenzuordnung oder Password-Aliasing.
Allgemeine Begriffe
Die Kenntnis einiger sicherheitsrelevanten Begriffe ist für das Verständnis der Java-EE-API und deren Spezifikation der Sicherheit hilfreich. JSR 375 bezieht sich wo sinnvoll auf die Terminologie des Apache-Shiro-Projekts und definiert einige zusätzliche Begriffe, die im neuen Standard für Java-Security verwendet werden.
Authentifizierungsmechanismus
Dieser Mechanismus kommuniziert mit dem Aufrufer der Anmeldeinformationen und ruft einen Identitätsspeicher auf, um die angegebenen Anmeldeinformationen zu einem bekannten Benutzer zu verifizieren (Identity). Wenn eine Übereinstimmung gefunden wird, verwendet der Authentifizierungsmechanismus die gefundene Identität, um Attribute (Principals) eines authentifizierten Elementes zu sammeln. Wenn keine Übereinstimmung gefunden wird, schlägt die Authentifizierung fehl.
Caller, Caller Principal
Ein Aufrufer (Caller) ist ein Benutzer, der eine Anfrage an eine Anwendung stellt oder eine Anwendungs-API aufruft. Ein Caller-Principal ist ein Hauptobjekt, das diesen Benutzer repräsentiert. Die Java-EE-Security-Spezifikation verwendet den Begriff Aufrufer in den meisten Fällen an Stelle des Begriffs Benutzer (User).
HAM
HAM ist die Abkürzung für HttpAuthenticationMechanism. Dabei handelt es sich um eine Schnittstelle, die durch die Java-EE-Security-Spezifikation definiert ist.
Identity-Store
Ein Identity-Store ist eine Komponente zum Zugriff auf anwendungsspezifische Sicherheitsdaten wie Benutzer, Gruppen, Rollen und Berechtigungen. Man kann es sich als sicherheitsspezifisches DAO (Data Access Object) vorstellen. Unterschiedliche Softwareanbieter verwenden dafür zahlreiche Synonyme: Sicherheitsanbieter, Repository, Shop, Login-Modul (JAAS), Identity-Manager, Service-Provider, Relying-Party, Authenticator oder User-Service. Identity-Stores haben in der Regel eine 1-zu-1-Korrelation mit einer Datenquelle wie einer relationalen Datenbank, einem LDAP-Verzeichnis, einem Dateisystem oder einer ähnlichen Ressource. Als solche verwenden Implementierungen der IdentityStore Schnittstelle datenquellenspezifische APIs zum Abruf von Berechtigungsdaten (Rollen, Berechtigungen, etc.), wie z.B. JDBC/Datenquellen, Dateisystem, JPA bzw. Hibernate oder andere Datenzugriffs-APIs.
JACC
JACC steht für den Java-Authorization-Contract für Container (JSR 115), Version 1.5.
JASPIC
JSR-196 beschreibt die Java-Authentifizierung SPI für Container, Version 1.0 .
SAM
SAM ist die Abkürzung für ServerAuthModule, eine von JASPIC definierte Schnittstelle.
Authentifizierung
Eine Web-Anwendung besteht aus Ressourcen, auf die von einer beliebigen Anzahl an Aufrufern zugegriffen werden kann, die der Anwendung anfänglich nicht bekannt sind. Aufrufer machen sich durch den Vorgang der Authentifizierung bei dieser Anwendung bekannt.
Während der Authentifizierung präsentiert der Aufrufer den Nachweis der Identität durch eine Zeichenfolge oder ähnliche Form der Berechtigung, welche die Applikation (oder der Container) dann validiert. Ist der Nachweis gültig, so stellt die Anwendung (oder ihr Container) die Identität des Aufrufers fest und fährt dann mit dem Schritt der Berechtigungsprüfung fort, um festzustellen ob der Aufrufer eine Berechtigung hat, auf die angeforderten Ressourcen zuzugreifen.
In einigen Fällen (z. B. Benutzername/Passwort-Authentifizierung) ist die Interaktion mit dem Aufrufer der Anwendung relativ einfach. In anderen Fällen ist ein längere Dialogabfolge erforderlich:
- eine Anwendung kann einen zufälligen Ausdruck an den Aufrufer schicken, welcher diesen Ausdruck dann zum Aufbau eines Authentifizierungs-Tokens nutzen muss, oder
- es kann Austausch mit einem Dritten geben die Identität des Aufrufers oder die Echtheit der vorgelegten Anmeldeinformationen.
Nutzer von Online-Banking-Systemen kennen dies so ähnlich in Form der benötigten Transaktionsnummer (TAN).
Die Java EE Plattform kennt im Rahmen der Servlet Spezifikation bereits Mechanismen zur Authentifizierung. Ergänzend dazu definiert der JASPIC3 Standard allgemeingültige Mechanismen zum Austausch sicherer Meldungen zwischen Java EE Client und Server. JASPIC ist sehr flexibel und mächtig, aber auch relativ kompliziert und nicht gerade leicht verständlich ohne Vorkenntnisse im Sicherheitsbereich. Mit ein Grund, weshalb sich oft proprietäre Lösungen einiger weniger Hersteller hier stark verbreitet haben. JSR 375 versucht, diese Situation zu verbessern.
Die Schnittstelle des HttpAuthenticationMechanism ist darauf ausgelegt, vorhandenen Stärken existierender Authentifizierungsmechanismen zu nutzen, während die entsprechenden Einschränkungen in der Benutzbarkeit gemildert werden. Es ist im Wesentlichen eine vereinfachte, Servlet-Container-spezifische Variante der JASPIC ServerAuthModule Schnittstelle, welche die Flexibilität und Leistungsfähigkeit der Schnittstelle beinhaltet, aber den Aufwand für die Implementierung zu reduzieren versucht. Ein HttpAuthenticationMechanism ist ein CDI-Bean und wird daher dem Container automatisch von CDI zur Verfügung gestellt. Der Container ist verantwortlich für die Bereitstellung des HttpAuthenticationMechanism als Service. Die HttpAuthenticationMechanism Schnittstelle definiert drei Methoden, die sich eng an jenen der JASPIC ServerAuth Schnittstelle orientieren. Der Unterschied ist hauptsächlich syntaktisch. Im Gegensatz zu JASPIC ist HttpAuthenticationMechanism nur für Servlet-Container vorgesehen und kann daher von Servlet-Typen in ihrer Methode von Signaturen Gebrauch machen. Es muss lediglich die validateRequest() Methode implementiert werden. Für die beiden anderen Methoden ist ein Standardverhalten definiert:
(Listing 1)
AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext ) throws AuthenticationException; AuthenticationStatus secureResponse(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext ) throws AuthenticationException; void cleanSubject(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext);
Die HttpAuthenticationMechanism Schnittstelle wird als CDI-Bean deklariert und ist damit via CDI im Java-EE-Container verfügbar. Der HttpAuthenticationMechanism wird mit Hilfe von Annotationen für folgende Arten der Authentifizierung angeboten:
- Basic – Mittels HTTP-Basic-Authentication,
- FORM – Nutzt die Form-Based-Authentication,
- Custom-FORM – Eine Erweiterte Variante der Form-Based-Authentication, bei der SecurityContext.authenticate() anstelle des Standard Servlet-Mechanismus aufgerufen wird.
(Listing 2) – Basic-Annotation
@Retention(RUNTIME) @Target(TYPE) public @interface BasicAuthenticationMechanismDefinition { /** * Name of realm that will be sent via the <code>WWW-Authenticate</code> header. * <p> * Note that this realm name <b>does not</b> couple a named identity store * configuration to the authentication mechanism. * * @return Name of realm */ String realmName() default „“; }
(Listing 3) – FORM-Annotation
@Retention(RUNTIME) @Target(TYPE) public @interface FormAuthenticationMechanismDefinition { @Nonbinding LoginToContinue loginToContinue(); }
(Listing 4) – Custom-FORM-Annotation
@Retention(RUNTIME) @Target(TYPE) public @interface CustomFormAuthenticationMechanismDefinition { @Nonbinding LoginToContinue loginToContinue(); }
(Listing 2) bis (Listing 4) zeigen Annotationen für die jeweilige Art der Authentifizierung. Zu weiteren Standard-Annotationen gehören @LoginToContinue und @RememberMe. Letztere wird in Verbindung mit einem RememberMeIdentityStore eingesetzt.
Identity Store
Der IdentityStore bietet die Abstraktion eines Identitätsspeichers, der etwa auf einer Datenbank oder einem Verzeichnis basieren kann und Identitätsinformationen über eine Menge an Benutzern als Aufrufer einer Anwendung enthält. Ein Identitätsspeicher beinhaltet etwa Aufrufernamen, Gruppenmitgliedschaftsinformationen und Informationen die ausreichen, um diese zu bestätigen. Ein Identitätsspeicher kann auch andere Informationen enthalten, wie global eindeutige Aufruferidentifikatoren (falls abweichend von den Aufrufernamen) oder anderen Aufruferattribute.
Implementierungen der IdentityStore Schnittstelle werden verwendet, um mit Identitätsspeichern zu interagieren, um Benutzer zu authentifizieren (d.h. ihre Anmeldeinformationen zu bestätigen) und um Aufrufer Gruppen zuzuordnen. IdentityStore ist vergleichbar mit einer JAAS LoginModule Schnittstelle, die oft in Java-EE-Produkten integriert ist (wenn auch in herstellerspezifischer, proprietärer Weise). Im Gegensatz zu LoginModule ist IdentityStore speziell für Java EE gedacht und bietet nur Berechtigungsvalidierungs- und Gruppenzugriffsfunktionen, d.h. Funktionen zur Interaktion mit einem Identitätsspeicher. Ein IdentityStore erhebt keine Anmeldeinformationen des Aufrufers oder manipuliert Attribute zu diesem.
(Listing 5) – IdentityStore
public interface IdentityStore { enum ValidationType { VALIDATE, PROVIDE_GROUPS } CredentialValidationResult validate(Credential credential); SSez<String> getCallerGroups(CredentialValidationResult validationResult); int priority(); Set<ValidationType> validationTypes(); }
In (Listing 5) ist die Definition der der IdentityStore Schnittstelle zu sehen. Die validate() Methode bestimmt, ob ein Credential gültig ist. Wenn ja, liefert sie Informationen über den, durch das Credential identifizierten, Benutzer. Es ist eine optionale Methode, die eine IdentityStore Implementierung nutzen kann oder auch nicht. Das Ergebnis der Validierung ist ein CredentialValidationResult.
Es existieren vordefinierte IdentityStore Beans für LDAP und Datenbankspeicher.
(Listing 6) – Database-IdentityStore-Annotation
@Retention(RUNTIME) @Target(TYPE) public @interface DataBaseIdentityStoreDefinition { String dataSourceLookup() default „java:comp/ DefaultDataSource“; String callerQuery() default „“; String groupsQuery() default „“; String hashAlgorithm() default „PBKDF2“; int priority() default 70; String priorityExpression() default „“; ValidationType[] useFor() default {VALIDATE, PROVIDE_GROUPS}; String useForExpression() default „“; }
Security Context
Die Java-EE-Plattform definiert ein deklaratives Sicherheitsmodell zum Schutz von Anwendungsressourcen. Deklarierte Einschränkungen für den Zugriff werden dabei durch den Container erzwungen. In einigen Fällen ist dieses deklarative Modell nicht ausreichend. Zum Beispiel, wenn eine Kombination von Tests und
Einschränkungen benötigt wird, die komplexer ist als das deklarative Modell es erlaubt. Programmatische Sicherheit ermöglicht die Anwendung von Tests, ob der Zugriff auf Ressourcen gewährt oder verweigert werden soll.
Dies erlaubt beispielsweise Aufrufer aus der Personalabteilung von jenen aus der Buchhaltung zu unterscheiden und gewissen Benutzern mehr Informationen zugänglich zu machen als anderen, sofern die Aufrufer Rollen wie System-Administrator, Manager oder Supervisor innehaben, um nur einige Beispiele zu nennen.
(Listing 7) – SecurityContext
Principal getCallerPrincipal(); <T extends Principal> Set<T> getPrincipalsByType(Class<T> pType); boolean isCallerInRole(String role);
Das SecurityContext Interface enthält wie aus (Listing 7) ersichtlich, eine getCallerPrincipal() Methode, die ein allgemeines Principal Objekt gemäß Java-Security zurückliefert. Die getPrincipalsByType() Methode erlaubt es, für bestimmte Container konkretere, maßgeschneiderte Principal-Implementationen
zu liefern und deren volle Funktionalität auszunutzen. Damit können auch vorhandene Lösungen, selbst wenn sie herstellerspezifische Erweiterungen anbieten, auf standardgemäße Art und Weise bereitgestellt werden.
isCallerInRole() frägt schließlich ab, ob der Aufrufer eine bestimmte Rolle erfüllt und dadurch die damit verbundenen Berechtigungen erhalten soll.
Weiterhin stellt SecurityContext eine Methode hasAccessToWebResource() bereit, mit der man beispielsweise durch securityContext.hasAccessToWebResource(„/protectedServlet“, GET) den Zugriff auf bestimmte Servletoder REST-Aufrufe kontrollieren und beschränken kann, zum Beispiel um nur bestimmten Aufrufern eine DELETE Methode zu erlauben, während GET einem breiteren Benutzerkreis zur Verfügung
steht.
Fazit:
Mit JSR 375 erhält Java EE 8 Sicherheitsfunktionen, die davor allenfalls produkt- und herstellerabhängig verfügbar waren und bietet eine deutlich bessere und einfachere Absicherung von Anwendungsteilen oder APIs als bisher. Einige weiterführende Wünsche und Extras wie OAuth bzw. OpenID-Connect wurden von Manchen schon als integraler Bestandteil des Standards, oder seiner Referenzimplementierung, erhofft. Doch bieten OAuth und Co. in der Praxis leider häufig so viel Freiräume bei deren Interpretation, dass fast jede größere API maßgeschneiderte Anpassungen und eigene Arten von Tokens und dergleichen nutzen. Das macht es schwer, eine allgemeingültige und fehlerfreie Lösung für alle denkbaren APIs und Anwendungs-Ressourcen anzubieten. JSR 375 bietet allerdings eine allgemeingültige, breit nutzbare Basis, auf der für die wichtigsten Dienste oder Protokolle in absehbarer Zeit wohl auch vorgefertigte Unterstützungen folgen dürften, wie man sie bereits jetzt für LDAP oder relationale Datenbanken als Identitätsspeicher findet. Eine jüngst für die Zeit nach Java EE 8 angedeutete Öffnung für mehr Anbieter, die neben Oracle stärker aktiv werden, könnte dabei ebenfalls hilfreich sein.
Werner Keil ist Java EE Consultant, BDD und Microservice Experte bei einem führenden Unternehmen im Logistik und IoT Bereich. Nach vergleichbarer Tätigkeit für Banken, Versicherungen und Unternehmen im Bereich Print und Automotive/Embedded, sowie als Agile Coach, Java Embedded und Eclipse RCP Experte bei einem Anbieter von Embedded und Realtime Systemen.