Java >> Java Tutorial >  >> Tag >> new

Was ist neu in Jakarta Security 3?

Trotz der Versionsnummer 3 ist Jakarta Security 3 das erste wirkliche Update von Jakarta Security, seit es als Java EE Security in Java EE 8 eingeführt wurde.

In diesem Artikel werfen wir einen Blick darauf, welche neuen Dinge hinzugefügt wurden. Wir werfen zunächst einen Blick auf die nutzerorientierte Umbrella-API, die Jakarta Security selbst ist, und werfen dann einen Blick auf die beiden zugrunde liegenden SPIs, von denen sie abhängt. Jakarta-Authentifizierung und Jakarta-Autorisierung.

OpenID Connect

Die charakteristische Ergänzung zu Jakarta Security 3 ist der neue OpenID Connect-Authentifizierungsmechanismus, der von Payaras Lead Developer Rudy De Busscher und Principal Engineer Gaurav Gupta beigesteuert wurde.

OpenID Connect verbindet die bestehenden Authentifizierungsmechanismen Basic, Form und Custom Form. Der Plan, durch Hinzufügen von Jakarta Security-Versionen der Client-Cert- und Digest-Authentifizierungsmechanismen auch Parität zu Servlet zu erreichen, ist leider gescheitert, da sich einfach niemand die Arbeit dafür genommen hat. Da Jakarta Security jetzt hauptsächlich ein von Freiwilligen betriebenes OSS-Projekt ist, ist das natürlich so; wir können planen, was wir wollen, sind aber letztendlich darauf angewiesen, dass Freiwillige Dinge aufgreifen.

OpenID Connect selbst ist ein Mechanismus, bei dem ein Drittanbieter-Server die tatsächliche Authentifizierung eines Endbenutzers übernimmt und das Ergebnis dieser Authentifizierung dann an unseren Server zurückgesendet wird. „Unser Server“ hier ist derjenige, auf dem Jakarta Security läuft, um unsere Webanwendung zu sichern. Da wir uns auf einen Drittanbieter-Server verlassen, wird dieser entfernte OpenID Connect-Server in der OpenID Connect-Terminologie als „Relying Party“ bezeichnet.

Dies ist im folgenden leicht angepassten Diagramm von der OpenID Connect-Website dargestellt:

    +--------+                                                       +--------+
    |        |                                                       |        |
    |        |---------------(1) Authentication Request------------->|        |
    |        |                                                       |        |
    |        |       +--------+                                      |        |
    |        |       |  End-  |<--(2) Authenticates the End-User---->|        |
    |   RP   |       |  User  |                                      |   OP   |
    |        |       +--------+                                      |        |
    |        |                                                       |        |
    |        |<---------(3) Returns Authorization code---------------|        |
    |        |                                                       |        |
    |        |---------(3b)                                          |        |
    |        |           | Redirect to original resource (if any)    |        |
    |        |<----------+                                           |        |
    |        |                                                       |        |
    |        |------------------------------------------------------>|        |
    |        |   (4) Request to TokenEndpoint for Access / Id Token  |        |
    | OpenID |<------------------------------------------------------| OpenID |
    | Connect|                                                       | Connect|
    | Client | ----------------------------------------------------->|Provider|
    |        |   (5) Fetch JWKS to validate ID Token                 |        |
    |        |<------------------------------------------------------|        |
    |        |                                                       |        |
    |        |------------------------------------------------------>|        |
    |        |   (6) Request to UserInfoEndpoint for End-User Claims |        |
    |        |<------------------------------------------------------|        |
    |        |                                                       |        |
    +--------+                                                       +--------+  
Siehe openid.net/specs/openid-connect-core-1_0

RP oder Relying Party ist der Jakarta EE-Server, auf dem unsere Webanwendung ausgeführt wird. OP oder OpenID Connect Provider ist der Remote-Server, der die Authentifizierung durchführt. Dies kann ein anderer Server sein, den wir selbst betreiben, oder häufiger ein öffentlicher Dienst wie Google, Facebook, Twitter usw.

Wir können diesen Authentifizierungsmechanismus in unseren eigenen Anwendungen über die neue Annotation „@OpenIdAuthenticationMechanismDefinition“ verwenden. Im Folgenden finden Sie ein Beispiel:

@OpenIdAuthenticationMechanismDefinition(
  
    providerURI =  "http://localhost:8081/openid-connect-server-webapp",

    clientId =     "client",

    clientSecret = "secret",
   
    redirectURI =  "${baseURL}/Callback",

    redirectToOriginalResource = true
)

Es gibt viele weitere Attribute, die konfiguriert werden können, aber das Obige ist eine typische Minimalkonfiguration. Schauen wir uns schnell an, was die verschiedenen Attribute bewirken.

Der „providerURI“ gibt den Standort des OpenID-Providers an. Hierhin wird der Endbenutzer umgeleitet, wenn er sich bei unserer Anwendung anmeldet. Die clientId und das clientSecret sind im Wesentlichen der Benutzername/das Passwort, um uns gegenüber dem OpenID-Anbieter zu identifizieren. Beachten Sie, dass bei einer Umleitung des Benutzers nur die clientId in die Umleitungs-URL eingefügt wird. Das clientSecret wird verwendet, wenn wir unsere sichere Server-zu-Server-Kommunikation durchführen, an der der Endbenutzer nicht beteiligt ist. Beachten Sie auch, dass Annotationsattribute in Jakarta Security Jakarta Expression Language enthalten können, und in der Praxis würden clientId und clientSecret auf diese Weise nicht in Konstanten im Code eingefügt.

Der Umleitungs-URI ist der Ort, an den der Endbenutzer nach der Authentifizierung zurückgeleitet wird. Hier wird ein spezieller Platzhalter ${baseURL} verwendet, der in die tatsächliche URL aufgelöst wird, für die unsere Anwendung bereitgestellt wird.

Schließlich haben wir die RedirectToOriginalResource, die sicherstellt, dass der Endbenutzer zurück zur ursprünglichen Ressource (Seite, Pfad) umgeleitet wird, wenn die Authentifizierung automatisch ausgelöst wurde, wenn auf eine geschützte Ressource zugegriffen wurde. Dies funktioniert genauso wie der bekannte FORM-Authentifizierungsmechanismus. Wenn auf false gesetzt, bleibt der Endbenutzer bei der Ressource hinter dem Umleitungs-URI, der dann offensichtlich existieren muss. Wenn es auf „true“ gesetzt ist, überwacht es der Authentifizierungsmechanismus, und es muss ihm kein tatsächliches Servlet oder kein Filter zugeordnet sein.

Das Folgende zeigt diese Anmerkung im Kontext eines Servlets:

@OpenIdAuthenticationMechanismDefinition(
    providerURI =  "http://localhost:8081/openid-connect-server-webapp",
    clientId =     "client",
    clientSecret = "secret",
    redirectURI =  "${baseURL}/Callback",
    redirectToOriginalResource = true
)
@WebServlet("/protectedServlet")
@DeclareRoles({ "foo", "bar", "kaz" })
@ServletSecurity(@HttpConstraint(rolesAllowed = "foo"))
public class ProtectedServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(
        HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, IOException {
        response.getWriter().write("This is a protected servlet \n");
    }

}
Bei Jakarta Security kombinieren wir normalerweise einen Authentifizierungsmechanismus mit einem Identitätsspeicher, der die Entität ist, die die vom Endbenutzer bereitgestellten Anmeldeinformationen validiert. Für OpenID Connect werden diese Endbenutzer-Anmeldeinformationen natürlich vom entfernten OpenID Connect-Anbieter validiert. Wir müssen zwar ein Token validieren, das vom Anbieter zurückkommt, aber das wird intern durch den Authentifizierungsmechanismus von OpenID Connect erledigt (es verwendet dafür einen Identitätsspeicher, aber einen internen).

Ein öffentlicher OpenID-Anbieter hat jedoch in der Regel keine Kenntnis von Gruppen, die ein Endbenutzer in unserer Anwendung hat, sodass wir genau für diesen Zweck einen Identitätsspeicher bereitstellen müssen. Dies ist im Grunde dasselbe, was wir oft für die Client-Zertifikat-Authentifizierung tun müssen, da die Zertifikate auch keine Gruppen enthalten. Im Folgenden finden Sie ein Beispiel für einen solchen Speicher:

@ApplicationScoped
public class AuthorizationIdentityStore implements IdentityStore {

    private Map<String, Set<String>> authorization = 
        Map.of("user", Set.of("foo", "bar"));

    @Override
    public Set<ValidationType> validationTypes() {
        return EnumSet.of(PROVIDE_GROUPS);
    }

    @Override
    public Set<String> getCallerGroups(CredentialValidationResult result) {
        return authorization.get(result.getCallerPrincipal().getName());
    }

}
Im Beispielgeschäft oben ordnen wir einen Endbenutzer namens „Benutzer“ den Gruppen „foo“ und „bar“ zu. Dieser Identitätsspeicher wird zusammen mit dem internen Identitätsspeicher von OpenID Connect aufgerufen, und unsere Aufgabe hier ist es, nur die Gruppen bereitzustellen.

Diese beiden Klassen können zusammen verpackt werden und eine vollständige Anwendung darstellen, die wir zum Testen verwenden können. Es ist hier als Maven-Projekt verfügbar:app-openid3

Kleine API-Verbesserungen

Neben der Ticketfunktion OpenID Connect wurden einige kleine API-Verbesserungen hinzugefügt:

CallerPrincipal Serialisierbar

Der native Principal-Typ, den Jakarta Security verwendet, um den Aufrufer-Principal zu bezeichnen, war in den ersten Versionen nicht serialisierbar. Dies verursachte verschiedene Probleme, wenn dieser Prinzipal in einer HTTP-Sitzung gespeichert wurde und eine Art Failover oder Clustering verwendet wurde. Es ist jetzt serialisierbar:

/**
 * Principal that represents the caller principal associated with the invocation 
 * being processed by the container (e.g. the current HTTP request).
 */
public class CallerPrincipal implements Principal, Serializable {

Interceptor dynamisch zu einer eingebauten CDI-Bean hinzufügen

Jakarta Security bietet eine Reihe von Interceptoren, die einer Bean Funktionalität hinzufügen, meist Beans, die Authentifizierungsmechanismen sind. Diese lassen sich leicht zu den eigenen Beans hinzufügen, erfordern jedoch etwas mehr Arbeit, um sie auf einen der in Jakarta Security integrierten Authentifizierungsmechanismen anzuwenden.

Zwei der Artefakte, die erstellt werden mussten, damit dies funktionierte, waren ein Wrapper für den HttpAuthenticationMechanism-Typ und ein Annotationsliteral für den Interceptor, den wir dynamisch hinzufügen wollten.

Diese Aufgabe wurde in Jakarta Security 3 ein wenig vereinfacht, wo alle Interceptors jetzt standardmäßige Annotationsliterale haben und der HttpAuthenticationMechanismWrapper-Typ jetzt von der API bereitgestellt wird.

Zum Beispiel:

@Inherited
@InterceptorBinding
@Retention(RUNTIME)
@Target(TYPE)
public @interface AutoApplySession {

    /**
     * Supports inline instantiation of the AutoApplySession annotation.
     *
     * @since 3.0
     */
    public static final class Literal extends AnnotationLiteral<AutoApplySession>
        implements AutoApplySession {
        private static final long serialVersionUID = 1L;

        /**
         * Instance of the {@link AutoApplySession} Interceptor Binding.
         */
        public static final Literal INSTANCE = new Literal();
    }
}

Jakarta-Authentifizierung

Die Jakarta-Authentifizierung ist die zugrunde liegende SPI, von der Jakarta Security abhängt. Verbesserungen hier kommen hauptsächlich Bibliotheksanbietern zugute, obwohl einige fortgeschrittene Benutzer sich auch für die direkte Verwendung entscheiden können.

ServerAuthModul registrieren

Der Endbenutzer der Jakarta-Authentifizierung sowie Integratoren wie Jakarta Security-Implementierungen kümmern sich fast immer nur um die Registrierung eines ServerAuthModule. Die AuthConfigFactory akzeptiert jedoch nur einen AuthConfigProvider, der im Wesentlichen ein „Wrapper-Wrapper-Wrapper-Wrapper“ eines ServerAuthModule für den Endbenutzer ist. Der AuthConfigFactory wurde eine neue Methode hinzugefügt, um nur ein ServerAuthModule zu registrieren.

Ein ServerAuthModule wird normalerweise in einem Servlet-Listener installiert. Das Folgende ist ein Beispiel:

@WebListener
public class SamAutoRegistrationListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        AuthConfigFactory
            .getFactory()
            .registerServerAuthModule(
                new TestServerAuthModule(),
                sce.getServletContext());
    }

}

Fehlende Generika zur API hinzufügen

Die Jakarta-Authentifizierung war merkwürdigerweise sogar in Jakarta EE 9.1, das offiziell auf Java SE 8 und 11 abzielt, bei Java SE 1.4. Dies bedeutete insbesondere, dass viele Generika überall in der API fehlten. Diese wurden nun hinzugefügt. Zum Beispiel:

public interface ServerAuthModule extends ServerAuth {

    void initialize(
        MessagePolicy requestPolicy, MessagePolicy responsePolicy, 
        CallbackHandler handler, Map<String, Object> options) 
        throws AuthException;

    Class<?>[] getSupportedMessageTypes();
}

Standardmethoden hinzufügen

Ein ServerAuthModule erfordert die Implementierung von Methoden für „secureResponse“ und „cleanSubject“, aber bei weitem nicht alle ServerAuthModule müssen dort etwas tun. Für diese Methoden wurden Standardwerte hinzugefügt, sodass Implementierungen, die diese nicht benötigen, etwas weniger ausführlich sein können. Die Oberfläche sieht nun wie folgt aus:
public interface ServerAuth {

    AuthStatus validateRequest(
        MessageInfo messageInfo, Subject clientSubject, 
        Subject serviceSubject) throws AuthException;

    default AuthStatus secureResponse(
        MessageInfo messageInfo, Subject serviceSubject) 
        throws AuthException {
        return AuthStatus.SEND_SUCCESS;
    }

    default void cleanSubject(
        MessageInfo messageInfo, Subject subject) 
        throws AuthException {
    }
}

Füge Konstruktor hinzu, der Ursache für AuthException nimmt

Die Jakarta-Authentifizierung auf Java SE 1.4-Ebene bedeutete, dass ihre AuthException die Einstellung der Ausnahmeursache, die in Java SE 5 hinzugefügt wurde, nicht nutzte.

Das Auslösen einer Ausnahme vom Jakarta-Authentifizierungscode war daher mehr als nur ein wenig ausführlich:

throw (AuthException) new AuthException().initCause(e);
Es wurden neue Konstruktoren hinzugefügt, die jetzt eine Ursache haben, sodass wir jetzt Folgendes tun können:
throw new AuthException(e);

Unterscheiden Sie zwischen dem Aufruf zu Beginn der Anfrage und dem Aufruf nach Authenticate()

Im Servlet-Container-Profil der Jakarta-Authentifizierung kann ein ServerAuthModule vom Container zu Beginn einer Anfrage (bevor Filter und Servlets aufgerufen werden) oder nach einem Aufruf von HttpServletRequest.authenticate() aufgerufen werden. Für ein ServerAuthModule gibt es keine Möglichkeit, zwischen diesen beiden Fällen zu unterscheiden, was manchmal für fortgeschrittenere Interaktionen erforderlich ist.

Ein ServerAuthModule kann dies nun überprüfen, indem es sich den Schlüssel „jakarta.servlet.http.isAuthenticationRequest“ in der Nachrichten-Info-Map ansieht.

Jakarta-Autorisierung

Die Jakarta-Autorisierung ist ein weiterer zugrunde liegender SPI, von dem Jakarta Security abhängt. Verbesserungen kommen auch hier hauptsächlich Bibliotheksanbietern zugute, obwohl einige fortgeschrittene Benutzer sich auch für die direkte Verwendung entscheiden können.

GetPolicyConfiguration-Methoden ohne Statusanforderung hinzufügen

Die PolicyConfigurationFactory in Jakarta Authorization verfügt über Methoden zum Abrufen einer Richtlinienkonfigurationsinstanz, die eine Sammlung von Berechtigungen enthält, die für Autorisierungsentscheidungen verwendet werden. Eine Richtlinie (Autorisierungsmodul) kann diese jedoch nicht ohne weiteres verwenden, da alle vorhandenen Methoden erforderliche Nebenwirkungen haben. In der Praxis muss eine solche Richtlinie daher auf implementierungsspezifische Wege zurückgreifen, die häufig die PolicyConfigurationFactory und die Richtlinie stark koppeln. Für die neue Version wurden Methoden hinzugefügt, um diese PolicyConfiguration direkt ohne Nebenwirkungen zu erhalten;
     public abstract PolicyConfiguration getPolicyConfiguration(String contextID);
     public abstract PolicyConfiguration getPolicyConfiguration();
Die erste Variante kann verwendet werden, wenn die Richtlinie bereits über die Kontext-ID (eine Kennung für die Anwendung) verfügt, während die zweite Variante eine bequeme Methode ist, die die PolicyConfiguration für die Kontext-ID zurückgibt, die im aufrufenden Thread festgelegt ist.

Methoden zu PolicyConfiguration hinzufügen, um Berechtigungen zu lesen

Die oben erwähnte PolicyConfiguration speichert die Berechtigungen, enthielt aber merkwürdigerweise zuvor keine Methoden, um diese Berechtigungen zurückzulesen. Eine Richtlinie musste immer auf implementierungsspezifische Methoden zurückgreifen, um diese Berechtigungen zu erhalten. Beispielsweise schrieb die PolicyConfiguration in alten Versionen von GlassFish ihre Berechtigungen zuerst in eine Richtliniendatei auf der Festplatte, und dann las die Richtlinie diese Datei zurück. Jetzt wurden endlich einige Methoden hinzugefügt, um die Berechtigungen direkt zurückzulesen:
     Map<String, PermissionCollection> getPerRolePermissions();
     PermissionCollection getUncheckedPermissions();
     PermissionCollection getExcludedPermissions();

Generischer Rückgabewert für getContext

Die Jakarta-Autorisierung hat ein PolicyContext-Objekt, von dem Instanzen verschiedener Typen erhalten werden können, vor allem das Subjekt. Die Signatur dieser Methode hat zuvor ein Objekt zurückgegeben, sodass immer ein Cast benötigt wurde. In der neuen Version wurde dies in einen generischen Rückgabewert geändert:public static T getContext(String key) throws PolicyContextExceptionAlso hat man das zB vorher getan:
     Subject subject = 
         (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");
Das kann jetzt sein:
     Subject subject = PolicyContext.getContext(“javax.security.auth.Subject.container");

Abschließende Gedanken

Die Anzahl der Änderungen für Jakarta Security 3 ist geringer als geplant, aber das große Ticket-Feature OpenID Connect ist sehr willkommen. Es war für die Erstveröffentlichung geplant, und einige Implementierungen hatten begonnen, haben es aber damals letztendlich nicht geschafft. Die Änderungen in den SPIs der unteren Ebene sind klein, aber einige von ihnen ziemlich wichtig. Mit Blick auf die Zukunft sollte die nächste Version von Jakarta Security sich mehr auf das Thema Autorisierung konzentrieren. Autorisierungsmodule sind in der aktuellen API immer noch etwas obskur, was schade ist, da es sich um ein sehr mächtiges Konzept handelt. Dieses Update hat dort die Voraussetzungen für eine zugänglichere zukünftige API geschaffen.
No
Java-Tag