GEDOPLAN
Jakarta EE (Java EE)

Authentifizieren mit OIDC und WildFly

Jakarta EE (Java EE)
Eine Tür mit einem Schild auf dem folgender Text steht: Restricted Area Authorized Personnel Only

Single-Sign-On (SSO) ist gerade im Enterprise Kontext wo eine zentrale Benutzerverwaltung stattfindet relevant. In WildFly gibt es schon seit vielen Jahren eine Unterstützung zur Authentifikation mittels OIDC, zum Beispiel in Zusammenspiel mit dem Keycloak Server aus demselben Hause. Seit dem letzten Blogpost zu diesem Thema hat es der Keycloak Adapter, der manuell hinzugefügt werden musste, als integriertes Subsystem in den WildFly Server geschafft. Wir verschaffen uns in diesem Beitrag einen Überblick über den aktuellen Stand und testen die Authentifizierung an einem einfachen Beispielprojekt.

Unsere Anwendung verfügt über eine REST-API und über eine Nutzeroberfläche. Die Nutzeroberfläche verwendet ein selbst definiertes Servlet sowie Jakarta Faces. Wir möchten gerne alle diese Schnittstellen über OIDC absichern. Dazu kann die Konfiguration über die Datei src/main/webapp/WEB-INF/oidc.json vorgenommen werden. Wir setzen hier Testwerte die optional über Umgebungsvariablen angepasst werden können.

{
  "client-id": "${env.OIDC_CLIENT_ID:demo-app}",
  "provider-url" : "${env.OIDC_PROVIDER_URL:http://localhost:9090/realms/demo}",
  "ssl-required": "external",
  "credentials": {
    "secret": "${env.OIDC_CLIENT_SECRET:QjfVYgcISONI4RTJSSyvqEf0D4NK1zmV}"
  },
  "principal-attribute" : "preferred_username"
}

Zusätzlich muss in der Datei src/main/webapp/WEB-INF/web.xml die Login-Methode OIDC aktiviert werden.

<login-config>
    <auth-method>OIDC</auth-method>
</login-config>

Absichern der Schnittstellen

Schauen wir uns zunächst die Faces Seiten. Es gibt eine öffentliche Startseite und einem authentifizierten Bereich im Pfad /auth. Die Zugriffsrechte und Beschränkung der Rollen können wir hierfür auch in der web.xml Datei anpassen. In diesem Fall konfigurieren wir einen Zugriff für alle authentifizierten Nutzer unabhängig von der Rolle. Hier ist aber natürlich auch die Beschränkung auf bestimmte Rollen möglich. Es können auch unterschiedliche Seiten für unterschiedliche Rollen freigegeben werden.

<security-role>
    <role-name>*</role-name>
</security-role>
<security-constraint>
    <web-resource-collection>
        <web-resource-name>Protected Web Services</web-resource-name>
        <url-pattern>/auth/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>*</role-name>
    </auth-constraint>
</security-constraint>

Unser @WebServlet ist unter /protectedServlet erreichbar. Es soll nur von Nutzern mit der Rolle demo-role aufgerufen werden können. Das können wir über die @ServletSecurity Annotation erreichen. Wir haben hier auch die Möglichkeit über den jakarta.security.enterprise.SecurityContext auf Attribute wie den Nutzernamen zuzugreifen.

@WebServlet("/protectedServlet")
@ServletSecurity(@HttpConstraint(rolesAllowed = "demo-role"))
public class ProtectedServlet extends HttpServlet {
  @Inject
  private SecurityContext securityContext;

  @Override
  public void doGet(
      HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    try (var writer = response.getWriter()) {
      writer.write("This is a protected servlet\n");
      writer.write("User: " + securityContext.getCallerPrincipal().getName() + "\n");
      writer.write("Is user in role demo-role?: " + securityContext.isCallerInRole("demo-role") + "\n");
    }
  }
}

Schauen wir uns nun die REST-API an. Jakarta Security definiert grundsätzlich eine @RolesAllowed Annotation. Damit RESTEasy diese Annotationen verarbeitet, muss dies zunächst in der web.xml Datei konfiguriert werden.

<context-param>
    <param-name>resteasy.role.based.security</param-name>
    <param-value>true</param-value>
</context-param>

Jetzt können wir mithilfe der Annotation den Zugriff auf bestimmte Endpunkte auf spezifische Rollen beschränken. In diesem Fall haben wir einen öffentlichen Endpunkt /api/demo/public und einen abgesicherten /api/demo/protected. Über SecurityContext erhalten wir Informationen über den angemeldeten Benutzer.

@RequestScoped
@Path("/demo")
public class DemoEndpoint {
    @Inject
    private jakarta.security.enterprise.SecurityContext securityContext;

    @RolesAllowed("demo-role")
    @GET
    @Path("protected")
    @Produces("text/plain")
    public String demoProtected() {
        return "securityContext User " + securityContext.getCallerPrincipal().getName();
    }

    @GET
    @Path("public")
    @Produces("text/plain")
    public String demoPublic() {
        return "Hello " + securityContext.getCallerPrincipal().getName() + "!";
    }
}

Das Beispielprojekt gibt es wie immer auch auf GitHub.

Aber gibt’s da nicht auch was vom Standard?

Seit Jakarta EE 10 gibt es die Annotation @OpenIdAuthenticationMechanismDefinition über die Applikationen mit OIDC Authentifizierung versehen werden können. Die Annotation muss an einer z.B. über @ApplicationScoped bereitgestellten Bean vorhanden sein und wird damit applikationsweit aktiviert. Die Verwendung ist aber an einen browserbasierten Authentifizerungsweg gebunden und eine REST Authentifikation über ein Bearer Token im Authorization Header, wie es dort üblicherweise eingesetzt wird, wird nicht unterstützt.

Für reine REST-Anwendungen ist MicroProfile-JWT eine mögliche Alternative. Über diesen mit Jakarta EE eng zusammenhängenden Standard lassen sich REST-Applikationen mit relativ wenig Aufwand absichern.

Problematisch ist in Jakarta EE 10 allerdings noch die Kombination dieser beiden Wege. Beide implementieren einen HttpAuthenticationMechanism und die Anwendung führt diese nicht einfach zusammen. Hier verspricht Jakarta EE 11 allerdings bereits Besserung und die Verwendung mehrerer Authentifikationsmechanismen soll vereinfacht werden. Diese Unterstützung ist in WildFly zum aktuellen Zeitpunkt leider noch nicht gegeben. Die Änderung sollte damit aber mittelfristig auch die kombinierte Absicherung einer UI- und REST-Schnittstelle mit Standardmitteln ermöglichen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Bitte füllen Sie dieses Feld aus.
Bitte füllen Sie dieses Feld aus.
Bitte gib eine gültige E-Mail-Adresse ein.
Sie müssen den Bedingungen zustimmen, um fortzufahren.

Autor

Diesen Artikel teilen

LinkedIn
Xing

Gibt es noch Fragen?

Fragen beantworten wir sehr gerne! Schreibe uns einfach per Kontaktformular.

Kurse

weitere Blogbeiträge

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!