Für ein Projekt musste ich ein PDF erzeugen und habe das dann mit iText umgesetzt.
Um mich in die Technik einzuarbeiten habe ich mir ein paar Bilder von Pixabay heruntergeladen, ein Projekt auf GitHub angelegt und dann schrittweise ein PDF mit Bildern erzeugt:
Heute durfte ich einen Impusvortrag zum Thema Quarkus halten. Den Inhalt der Folien incl. Kommentare und die Notizen der Live Demo kopiere ich hierher.
Quarkus
Was ist Quarkus?
• Open-Source-Framework • Um Anwendungen für eine moderne, Cloud-native Welt zu erstellen • Kubernetes-natives Java-Framework • auf GraalVM und HotSpot zugeschnitten • aus den besten Java-Bibliotheken und -Standards entwickelt
Was ist Quarkus?
Traditionelle Java-Stacks wurden für monolithische Anwendungen mit langen Startzeiten und großem Speicherbedarf in einer Welt entwickelt, in der es noch keine Cloud, Container und Kubernetes gab. Java-Frameworks mussten sich weiterentwickeln, um den Anforderungen dieser neuen Welt gerecht zu werden.
Quarkus wurde entwickelt, um Java-Entwicklern die Möglichkeit zu geben, Anwendungen für eine moderne, Cloud-native Welt zu erstellen. Quarkus ist ein Kubernetes-natives Java-Framework, das auf GraalVM und HotSpot zugeschnitten ist und aus den besten Java-Bibliotheken und -Standards entwickelt wurde. Ziel ist es, Java zur führenden Plattform in Kubernetes- und Serverless-Umgebungen zu machen und Entwicklern ein Framework zu bieten, das eine größere Bandbreite an verteilten Anwendungsarchitekturen abdeckt.
Vollständig und absolut Open Source
https://quarkus.io/about/
Ausgewählte Features
• Live-Coding mit Dev-Modus • Microprofile-Integration für Cloud-native Anwendungen • Nutzung von Quarkus für RESTful-Anwendungen • Serverless Funktionen mit Quarkus Funqy • Integration mit Datenbanken • Performance • Erweiterungen
Live-Coding mit Dev-Modus
Eine der herausragenden Eigenschaften von Quarkus ist die Möglichkeit des Live-Codings. Mit dem Dev-Modus können Entwickler Änderungen am Code vornehmen und diese Änderungen werden sofort in der laufenden Anwendung wirksam, ohne dass ein Neustart erforderlich ist. Dies beschleunigt den Entwicklungsprozess erheblich und ermöglicht eine iterative Entwicklung in Echtzeit.
Serverless Funktionen mit Quarkus Funqy
Eine API für verschiedenen FaaS-Umgebungen wie AWS Lambda, Azure Functions, Google Cloud Functions, Knative und Knative Events (Cloud Events), daher eine sehr einfache API.
Quarkus Funqy ist Teil der serverlosen Strategie von Quarkus und zielt darauf ab, eine portable Java-API zum Schreiben von Funktionen bereitzustellen, die in verschiedenen FaaS-Umgebungen wie AWS Lambda, Azure Functions, Google Cloud Functions, Knative und Knative Events (Cloud Events) eingesetzt werden können. Es ist auch als eigenständiger Dienst nutzbar.
Da es sich bei Funqy um eine Abstraktion handelt, die mehrere verschiedene Cloud-/Funktionsanbieter und Protokolle umfasst, muss es eine sehr einfache API sein und verfügt daher möglicherweise nicht über alle Funktionen, die Sie von anderen Remoting-Abstraktionen gewohnt sind. Ein schöner Nebeneffekt ist jedoch, dass Funqy so optimiert und so klein wie möglich ist. Das bedeutet, dass Funqy zwar ein wenig an Flexibilität einbüßt, dafür aber einen Rahmen bietet, der wenig bis gar keinen Overhead hat.
Integration mit Datenbanken
Quarkus bietet Erweiterungen für verschiedene Datenbanken, wie z.B. PostgreSQL, MySQL, MongoDB und viele mehr.
Performance
Erweiterungen
Live Demo
Download
Import into Eclipse IDE
Prerequisites
Eclipse öffnen, Terminal mit Ubuntu (WSL) öffnen
Java und Maven Versionen überprüfen:
java --version
echo $JAVA_HOME
mvn --version
# bei mir wird die Maven Version nicht angezeigt, wohl aber der Pfad der Installation, über den prüfe ich die Version
ls - lisah /opt/maven
cd /mnt/c/dev/workspace/code-with-quarkus/
clear
Start Quarkus
Starte Projekt:
./mvnw compile quarkus:dev
Internal Web Browser öffnen: http://localhost:8080/ „Visit the DEV UI“ funktioniert nicht im Eclipse Browser
Return String ändern und hello-Seite neu laden -> kein Neustart notwendig!
curl http://localhost:8080/hello
# In Klasse GreetingResource ändern von:
@Produces(MediaType.TEXT_PLAIN)
# nach:
@Produces(MediaType.APPLICATION_JSON)
Änderung wird auch hier ohne Neustart übernommen, um sie zu zeigen verwende ich HTTPie statt cURL:
http http://localhost:8080/hello
Um das Hot Deployment für JUnit Tests zu zeigen gehe ich in das Terminal, mit dem ich Quarkus gestartet habe und drücke "r" für "re-run test". Der Test schlägt fehlt, da ich in GreetingResource den Rückgabestring geändert hatte. Ich passe den zu erwartenden Wert in GreetingResourceTest an und drücke dann wieder "r" im Terminal. Alle Tests sind jetzt grün. Es war kein Neustart notwendig.
Contexts and Dependency Injection (CDI)
Klasse GreetingService hinzufügen:
package org.acme;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class GreetingService {
private String greeting = "Hallo";
public String greeting(String name) {
return greeting + " " + name;
}
}
Die Klasse GreetingResource erweitern:
package org.acme;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class GreetingResource {
@Inject
GreetingService service;
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from Welt";
}
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/greeting/{name}")
public String greeting(String name) {
return service.greeting(name);
}
}
Und wieder ohne Neustart testen:
http http://localhost:8080/hello/greeting/Welt
Configuration
Klasse GreetingService ändern:
package org.acme;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class GreetingService {
@ConfigProperty(name = "greeting")
private String greeting;
public String greeting(String name) {
return greeting + " " + name;
}
}
Testen:
http http://localhost:8080/hello/greeting/Welt
Der Test schlägt fehl, da die Property greeting noch nicht gesetzt wurde, was wir jetzt nachholen:
greeting=Huhu
Testen:
http http://localhost:8080/hello/greeting/Welt
Jetzt funktioniert es und es kommt "Huhu Welt" zurück.
Staged Properties: In DEV Modus möchten wir eine andere Begrüßung sehen:
greeting=Huhu
%%dev.greeting=Moin
Testen:
http http://localhost:8080/hello/greeting/Welt
Es funktioniert und es kommt "Moin Welt" zurück.
Metrics
Testen:
http http://localhost:8080/q/metrics
Fehler: Not Found
Mit Maven nachinstallieren (alternativ: Gradle oder Quarkus CLI):
Ich möchte eine lokale Oracle Datenbank mit Docker laufen lassen um so einige Sachen schnell lokal testen zu können. Hintergrund ist eine anstehende Cloud zu Cloud Migration einer bestehenden Anwendung, bei der zugleich die Oracle DB und Java aktualisiert werden wird.
Docker Image
Bei PostgreSQL war das mit der gedockerten Datenbank relativ einfach. Oracle macht es etwas schwieriger. Einfache Images, die man auf dem Docker Hub finden kann, existieren nicht. Statt dessen muss man ein GitHub Repository clonen und ein Shell Script ausführen, um ein Image zu erzeugen und in die lokale Registry zu schieben.
Frei verfügbar sind nur die Versionen Oracle Database 18c XE, 21c XE and 23c FREE. Ich entscheide mich, für die beiden Versionen 21c XE und 23c FREE das Image zu erzeugen und dann zuerst mit Version 23c FREE zu testen und ggf. später weitere Tests mit Version 21c XE machen zu können.
cd <workspace>
mkdir oracle
cd oracle
git clone https://github.com/oracle/docker-images.git
cd docker-images/OracleDatabase/SingleInstance/dockerfiles/
./buildContainerImage.sh -h
./buildContainerImage.sh -f 23.2.0
# Oracle Database container image for 'free' version 23.2.0 is ready to be extended:
#
# --> oracle/database:23.2.0-free
#
# Build completed in 608 seconds.
./buildContainerImage.sh -x 21.3.0
# Version 23.2.0 does not have Express Edition available.
Die Erzeugung des zweiten Images hat leider nicht funktioniert. Da das erste Image schon so lange gebraucht hat und ich das zweite Image nur proaktiv anlegen wollte, bin ich auch momentan nicht großartig motiviert, dem jetzt weiter nachzugehen. Version 23c FREE reicht erst einmal.
Image direkt von Oracle
Nach dieser Doku kann man das Image auch direkt aus der Oracle Registry ziehen. Zumindest für Oracle Database 23c Free – Developer Release.
Docker Container
Die Dokumentation hat einen speziellen Abschnitt für 23c FREE
Den Abschnitt auf jeden Fall gut ansehen, ich habe den Container mit folgendem Befehl erzeugt:
Spring Initializr verwenden. Als Dependencies Spring Web und OAuth2 Client hinzufügen.
Zip-File herunterladen und in das Projekt entpacken. Warten bis Maven alle Dependencies heruntergeladen hat und anschließend Oauth2Application starten.
Unter http://localhost:8080 wird eine Login-Seite angezeigt:
Add a Home Page
Hier halte ich mich an den Guide. Die WebJars kannte ich bereits, aber noch nicht deren Locator. Den muss ich mal in meinem JSF Projekt ausprobieren.
In your new project, create index.html in the src/main/resources/static folder. You should add some stylesheets and JavaScript links so the result looks like this:
None of this is necessary to demonstrate the OAuth 2.0 login features, but it’ll be nice to have a pleasant UI in the end, so you might as well start with some basic stuff in the home page.
If you start the app and load the home page, you’ll notice that the stylesheets have not been loaded. So, you need to add those as well by adding jQuery and Twitter Bootstrap:
The final dependency is the webjars "locator" which is provided as a library by the webjars site. Spring can use the locator to locate static assets in webjars without needing to know the exact versions (hence the versionless /webjars/** links in the index.html). The webjar locator is activated by default in a Spring Boot app, as long as you don’t switch off the MVC autoconfiguration.
With those changes in place, you should have a nice looking home page for your app.
Oauth2Application starten. Mir wird wieder die Login-Seite angezeigt. Ich nehme die spring-boot-starter-oauth2-client Dependency aus der pom.xml heraus, starte Oauth2Application neu und sehe eine leere Seite.
Mir ist nicht ganz klar, woran das liegt, also mache ich erstmal weiter mit dem Guide, in der Hoffnung dass es am Ende laufen wird.
Add a New GitHub App
Webhook: Active Flag deaktivieren
MÖÖÖÖÖÖP FALSCH
Ich habe oben eine GitHub App angelegt und keine OAuth App. Also nochmal von vorn:
In einem anderen Tab des Browsers war ich bereits in GitHub eingeloggt:
Wenn ich den Seitenaufruf in einem neuen privaten Browserfenster tätige, sieht die Anmeldung so aus:
In beiden Fällen wird anschließend eine leere Seite angezeigt:
Auch die index.html bleibt leer:
Also irgendwas ist da noch nicht richtig. Anscheinend hatte ich die index.html nicht richtig gespeichert, die Datei war noch leer. Also den Code hinzugefügt und gespeichert.
Anschließend funktioniert es auch:
Sehr schön, da kann der Zukunftsingo sich ja freuen, hier weiter herumzuexperimentieren, sobald wieder etwas Zeit ist.
Ein Report soll als Word Dokument (.docx) verfügbar gemacht werden.
Dazu wird zuerst eine Word Datei als Template erstellt mit Platzhaltern an den entsprechenden Stellen. Die Ersetzung einzelner Wörter ist relativ trivial, das Befüllen einer Tabelle mit n Zeilen stellte sich dann etwas kniffliger heraus. Diese Notizen und das dazugehörige Beispielprojekt dienen als Erinnerungsstütze, falls der Zukunftsingo nach dem aktuellen Projekt noch einmal etwas mit docx4j machen darf.
Word Template erstellen
Word mit einem leeren Dokument öffnen, einen Platzhalter für eine Überschrift, einen Text und eine Tabelle hinzufügen.
Die Überschriften der Tabelle sind fix und werden nicht ersetzt. Die Anzahl der Zeilen in dem Report ist dynamisch. Die Tabelle erhält keine Zeilen, diese werden später hineingeneriert.
Würde der Report eine Tabelle mit einer fixen Anzahl von Zeilen vorsehen, könnte man die ganze Tabelle in das Dokument eintragen und und später die einzelnen Werte per Wortersetzung hineingenerieren.
Die Tabelle bekommt noch einen Titel, um sie so eindeutig identifizieren zu können. Beispielsweise wenn das Dokument mehrere Tabellen enthält.
Abschließend unter dem Namen template.docx als Word-Dokument speichern, nicht als Strict Open XML-Dokument!
Projekt einrichten
Ein neues Maven Projekt anlegen.
Die Datei template.docx nach src/main/resources kopieren.
Java Klasse anlegen, die das Template läd und eine neue Zieldatei im tmpdir mit teilgeneriertem Name anlegt. Dadurch erhalten wir bei jedem Durchlauf eine neue Datei, um sie ggf. noch einmal miteinander vergleichen zu können, und das Aufräumen übernimmt das Betriebssystem für uns.
Als Dummyaction wird das Template in die Zieldatei kopiert. An dieser Stelle kommt im nächsten Schritt Docx4J ins Spiel um das Template zu befüllen.
Im letzten Schritt lassen wir uns die Zieldatei mit Word öffnen um so direkt das Ergebnis überprüfen zu können.
Intern benutzt Java UTF-16, "extern" wurde bisher standartmäßig das Encoding des Betriebssystems, zB Windows-1252, verwendet. Das hatte in der Vergangenheit immer wieder zu lustigen Encoding Problemen geführt, so dass ich schon lange meine Projekte und IDE Einstellungen auf UTF-8 umgestellt hatte. Ausnahme sind die Property-Dateien, die erst ab Java 9 UTF-8 können. Und Java 9 ist noch lange nicht in allen Projekten verfügbar.
Ich habe ein kleines Testprojekt aufgesetzt und dafür Java 19 verwendet. Dabei stieß ich irgendwann auf ein Encoding Problem, dass sich recht einfach nachstellen ließ:
package deringo;
public class TestMain {
public static void main(String[] args) {
String s = "abc äüß def";
System.out.println(s);
}
}
Output:
Projekt auf Java 17 umgestellt und der Output ist korrekt:
Die IDE ist Eclipse und der Bug ist bekannt: Bug 579383
Mit Java 17 wurde, als Vorbereitung auf JEP400, eine neue System Property eingeführt: native.encoding
We have a very, very old application that needs to be migrated into AWS. So we copied all files into AWS EC2 instance and tried to start the application. After fixing a lot of minor problems we faced a tough challenge with a SAPJCO RFC Call.
The Exception message was something like this:
Exception in thread "main" java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC'
JCO.nativeInit(): Could not initialize dynamic link library sapjcorfc [sapjcorfc (Not found in java.library.path)]. java.library.path [/usr/lib/jvm/java-1.6.0-ibm.x86_64/jre/lib/amd64/default:/usr/lib/jvm/java-1.6.0-ibm.x86_64/jre/lib/amd64:/usr/lib]
at com.sap.mw.jco.JCO.<clinit>(JCO.java:871)
at java.lang.J9VMInternals.initializeImpl(Native Method)
at java.lang.J9VMInternals.initialize(J9VMInternals.java:199)
I guess, with a JCO Version 3 we would not have much trouble, but in this ancient application JCO Version 2 is used and we cannot update to Version 3 without a huge efford. In other projects I had the luck that I could migrate to Version.
The application is running on a Linux system. But belive me: it would have been much harder on a Windows machine.
Analysis
To find the cause of the problem I wrote the simpliest JCO Test Programm I can image:
import com.sap.mw.jco.JCO;
public class TestMain {
public static void main(String[] args) {
System.out.println(JCO.getVersion());
}
}
Exception in thread "main" java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC'
JCO.nativeInit(): Could not initialize dynamic link library sapjcorfc [/app/JCo/libsapjcorfc.so: librfccm.so: cannot open shared object file: No such file or directory]. java.library.path [/app/JCo]
at com.sap.mw.jco.JCO.<clinit>(JCO.java:871)
at TestMain.main(TestMain.java:11)
Need to set an environment property first:
export LD_LIBRARY_PATH=/app/JCo
Run command line to start programm again and got another error:
Exception in thread "main" java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC'
JCO.nativeInit(): Could not initialize dynamic link library sapjcorfc [/app/JCo/libsapjcorfc.so: libstdc++.so.5: cannot open shared object file: No such file or directory]. java.library.path [/app/JCo]
at com.sap.mw.jco.JCO.<clinit>(JCO.java:871)
at TestMain.main(TestMain.java:11)
The interesting part of the error message:
Could not initialize dynamic link library sapjcorfc [/app/JCo/libsapjcorfc.so: libstdc++.so.5
Solution
We need the libstdc++.so.5 library, but installed is libstdc++.so.6
To get libstdc++.so.5 we installed package compat-libstdc++-33-3.2.3-66.x86_64:
yum install compat-libstdc++-33-3.2.3-66.x86_64
## to be honest, I am not exactly 100%% sure, what I did in my investigations, so the command may be a little differend, ex:
# yum install compat-libstdc++-33-3
# yum install compat-libstdc++-33
# yum install compat-libstdc++-33 libstdc++.so.5
Test
Run from command line:
java -cp ".:/app/JCo/sapjco.jar" TestMain
That gave me no error, but SAPJCo Version number.:
libstdc++.so.5 is a very old version of the standard c++ library.
Some Analysis Details
Writing this article is giving me the feeling, that this was all super easy. But in reality it was a real pain in the allerwertesten.
To isolate the source of the problem, I did not only write the small Java (JCO.getVersion) application, I also set up a Docker environment.
One challenge was to find a useful Docker image to start from. I started with an OpenJDK Image that was already deprecated. Deprecated was not the problem, but I could not install libstdc++.so.5.
Next I tried to use the newer, undeprecated Eclipse-Temurin Image. But still could not install libstdc++.so.5
So I finally ended in a Debian Image and self installed Java where I was able to install libstdc++5.
But there is one problem: You can only interact with a running container. But the TestMain-Programm is executed and immediately closed.
So I wrote another Test Programm, that keeps running, so I can enter the running container and test stuff (install packages, compile Java programm, etc.):
import java.io.BufferedReader;
import java.io.InputStreamReader;
import com.sap.mw.jco.JCO;
public class TestMain {
public static void main(String[] args) {
System.out.println("Hello World");
//System.out.println("JCO Version: " + JCO.getVersion());
while (true) {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Enter Input : ");
try {
String s = br.readLine();
System.out.println(s);
}catch(Exception e) {
System.out.println(e);
}
}
}
}
Wir wollen unsere Anwendung durch einen Filter schützen, der nur Anfragen von eingeloggten Benutzern mit der richtigen Rolle hindurch lässt.
Für diesen PoC bauen wir einen Reverse Proxy für den Login und einen Anwendungsserver für den Filter. Der Anwendungsserver bekommt einen Zugang über den RP und einen Zugang ohne RP, um so zu zeigen, dass nur über den RP auf die Anwendung zugegriffen werden kann.
In produktiven Umgebungen darf es den Zugang ohne RP so ungeschützt nicht geben, da es sehr einfach ist, zB über ein Browser-Plugin, den RP mittels selbstgesetzter Header zu faken.
Reverse Proxy
Der Reverse Proxy bekommt grundsätzlich die gleiche Konfiguration wie in den Beispielen zuvor.
Hier müssen wir aber noch den Scope params hinzufügen, um so die OneLogin-Rollen des Benutzers zu übertragen:
## OIDCScope params
## to put params including roles into header
OIDCScope "openid email profile groups params"
Im nächsten Fenster des Dialoges die Value ("User Roles") setzen:
Access Filter
Der Access Filter prüft, ob eine UserID vorhanden ist. Das ist in unserem Beispiel die Email, es könnte aber auch der Preferred Username genommen werden.
Des weiteren prüft der Access Filter, ob der Benutzer die erforderliche Rolle user hat.
Sind beide Bedingungen erfüllt, kann auf die Anwendung zugegriffen werden, ansonsten wird lediglich "Access denied" angezeigt.
Im Docker File des Anwendungsservers kopieren wir erst nur das Maven POM, lassen dann Maven bauen, um so die Abhängigkeiten herunterzuladen. Erst danach kopieren wir das gesamte Projekt in den Container und lassen die Anwendung bauen. So wird nach Code Änderungen, ohne Anpassungen in Maven, der Bau des Images beschleunigt, da nicht jedes Mal die Bibliotheken heruntergeladen werden müssen.
FROM tomcat:8.5-jdk8-openjdk-slim
RUN apt update && apt install -y \
maven
COPY pom.xml /app/pom.xml
WORKDIR /app
RUN mvn package
COPY . /app
RUN mvn package
WORKDIR $CATALINA_HOME
RUN mv /app/target/ROOT.war webapps
EXPOSE 8080
CMD ["catalina.sh", "run"]
Test
Zugriff auf den Anwendungsserver über den Reverse Proxy nach Anmeldung:
http://localhost/private/test.html
Direkter Zugriff auf den Anwendungsserver ohne Anmeldung:
Im vorherigen Post haben wir gesehen, dass ein neuer Benutzer sich selbst registrieren kann, aber um den Zugang zur Anwendung zu erhalten, er noch durch einen User-Admin freigeschaltet/der Anwendung hinzugefügt werden muss.
Das Hinzufügen des Benutzers erfolgt in OneLogin, so dass der User-Admin auch in diesem Tool geschult werden müsste.
Um den Schulungsaufwand zu vermeiden und ein flüssigeres Arbeiten, ohne Wechsel der Anwendungen, zu ermöglichen, wollen wir einige Funktionalität von OneLogin in unserer Anwendung ermöglichen. Dazu gibt es in OneLogin eine API-Schnittstelle, die wir mit einem Java-Client ansteuern werden.
Der Java-Client logt sich einmalig mit Client ID und Secret (API Credentials! siehe unten) ein und erhält so ein Access Token, mit dem die weiteren API-Funktionen ausgeführt werden können.
Die API-Dokumentation, für Version 2, findet sich hier: https://developers.onelogin.com/api-docs/2/getting-started/dev-overview
Vorbereitung / Daten sammeln
API-Subdomain
Die API-Domain des Testprojekt ist: deringo-dev.onelogin.com
Die API-Sub-Domain lautet foglich: deringo-dev
API ID & Secret
Für die Benutzung der API wird Client ID und Client Secret benötigt. Das kennen wir schon aus einem vorherigen Post zu OneLogin:
Für den Zugriff über die API benötigen wir aber ein anderes Client ID & Secret Paar! Dieses findet sich in dem Developers-Tab unter API Credentials.
Dort ist ein entsprechender Zugang einzurichten, für das Test-Projekt haben wir mit Manage All eingerichtet:
OneLogin App ID
Die ID unserer Test-Anwendung findet sich in der URL nachdem man auf Applications -> Applications die App auswählt, in unserem Beispiel "1739301":
Java Programm aufsetzen
Abhängigkeiten
Das Projekt wird für Java 8 aufgesetzt und benötigt Apache HttpClient 4.5 und Json:
Zum Testen der API-Funktionen wird eine Test-Klasse mit den oben gesammelten Informationen angelegt und dann jeweils eine eigene Methode in der Klasse verwendet.
public class TestMain {
private static final String ONELOGIN_SUBDOMAIN = "deringo-dev";
// OneLogin Administration -> Applications -> select Application, get ID from URL
private static final String ONELOGIN_APP_ID = "1739301";
// OneLogin Administration -> Developers -> API Credentials
private static final String ONELOGIN_CLIENT_ID = "12345<geheim>67890";
private static final String ONELOGIN_CLIENT_SECRET = "12345<geheim>67890";
public static void main(String[] args) {
TestMain main = new TestMain();
main.sayHello();
}
private void sayHello() {
System.out.println("Hello World!");
}
}
Wenn zB nur die Rollen für unsere Test-App angezeigt werden sollen und der Rollen Name ein "u" enthalten muss, setzt sich der resourceURL-String wie folgt zusammen:
Wollen wir uns anstelle der gesamten User-Information lediglich Vor- und Nachname zurückgeben lassen, neben der ID, dann brauchen wir lediglich den Query Parameter anpassen:
Seitenweises Vor- oder Zurückblättern ist möglich, dazu gibt es den Parameter "cursor". Im Header der ersten Paginierten Response gibt es, falls vorhanden, After-Curser und Before-Curser. Über diese Werte kann vor- und zurückgeblättert werden.
Beispielsweise um die erste Seite zu erhalten und anschließend eine Seite weiter blättern:
Es soll zuerst ein neuer User angelegt werden. Anschließend bearbeitet, der Anwendung hinzugefügt und wieder entfernt und abschließend gelöscht werden.
HTTP/1.1 201 Created
{"firstname":null,"updated_at":"2022-06-01T14:34:52.423Z","role_ids":[],"invitation_sent_at":null,"member_of":null,"distinguished_name":null,"email":null,"id":178924216,"password_changed_at":null,"manager_ad_id":null,"group_id":null,"invalid_login_attempts":0,"phone":null,"title":null,"preferred_locale_code":null,"department":null,"samaccountname":null,"lastname":null,"custom_attributes":{"my_role":null},"created_at":"2022-06-01T14:34:52.423Z","directory_id":null,"locked_until":null,"status":7,"company":null,"activated_at":null,"last_login":null,"trusted_idp_id":null,"manager_user_id":null,"username":"min.requirements","comment":null,"external_id":null,"state":1,"userprincipalname":null}
new User ID: 178924216
Nächstes Beispiel mit Vor-, Nach- und Usernamen, Passwort und schönerem JSON:
HTTP/1.1 201 Created
{"firstname":"Happy","updated_at":"2022-06-01T14:43:30.646Z","role_ids":[],"invitation_sent_at":null,"member_of":null,"distinguished_name":null,"email":null,"id":178924824,"password_changed_at":"2022-06-01T14:43:30.629Z","manager_ad_id":null,"group_id":null,"invalid_login_attempts":0,"phone":null,"title":null,"preferred_locale_code":null,"department":null,"samaccountname":null,"lastname":"Gilmore","custom_attributes":{"my_role":null},"created_at":"2022-06-01T14:43:30.646Z","directory_id":null,"locked_until":null,"status":1,"company":null,"activated_at":"2022-06-01T14:43:30.644Z","last_login":null,"trusted_idp_id":null,"manager_user_id":null,"username":"happy.gilmore","comment":null,"external_id":null,"state":1,"userprincipalname":null}
new User ID: 178924824
Unser neuer User Happy Gilmore ist angelegt und kann sich mit seinem Username & Passwort auch anmelden, kommt aber, wie erwartet, noch nicht auf die Anwendung, da er ihr noch nicht zugewiesen wurde:
HTTP/1.1 200 OK
{"firstname":"Very Happy","state":1,"manager_user_id":null,"department":null,"comment":null,"created_at":"2022-06-01T14:43:30.646Z","email":null,"last_login":"2022-06-01T14:46:10.795Z","lastname":"Gilmore","activated_at":"2022-06-01T14:43:30.644Z","directory_id":null,"updated_at":"2022-06-01T14:57:22.656Z","group_id":null,"samaccountname":null,"status":1,"userprincipalname":null,"trusted_idp_id":null,"title":null,"manager_ad_id":null,"invalid_login_attempts":0,"locked_until":null,"phone":null,"role_ids":[],"invitation_sent_at":null,"company":null,"distinguished_name":null,"external_id":null,"password_changed_at":"2022-06-01T14:43:30.629Z","preferred_locale_code":null,"id":178924824,"member_of":null,"username":"happy.gilmore","custom_attributes":{"my_role":null}}
User ID: 178924824
Firstname: Very Happy
User einer Anwendung hinzufügen
Als nächstes sollte der User einer Anwendung hinzugefügt werden, um so den Zugang zu gewähren.
Überraschenderweise gibt es aber keine API, um einer App einen User hinzuzufügen. irgendwie ist das nicht ganz nachzuvollziehen, da dies über die OneLogin-Webseite möglich ist und es einen API Call gibt, um alle User einer App anzeigen zu lassen (List App Users - OneLogin API).
Ein Erklärungs- und somit Lösungsansatz lautet:
The proper way to do this is to assign applications to a Role in the admin console and then use the APIs to set the roles for the user.
This, in turn grants the user access to the application assigned to the role.
Die Überprüfung in der OneLogin Webseite ergibt, dass der User in der Tat hinzugefügt wurde, und es wurden auch keine vorhandenen User aus der Rolle entfernt, die nicht in dem Array enthalten waren.
Unser neuer User Happy Gilmore ist angelegt und kann sich mit seinem Username & Passwort auch anmelden, kommt jetzt auf die Anwendung, da er ihr über die Rolle "user" zugewiesen wurde:
Um Daten von einem bestimmten Webservice beziehen zu können, muss der Aufruf durch einen authentifizierten User erfolgen. Der Webservice steht im Intranet des Kunden und der Aufruf im Browser funktioniert ohne Authentifizierung, denn im Hintergrund wird der Windows User übermittelt.
Der Aufruf über Java funktioniert nicht, da kein User übermittelt wird.
Die Authentifizierung am Webservice erfolgt lt. Ansprechpartner durch eine Windows Authentifizierung, was technischer ausgedrückt NTLM sein sollte.
Test Projekt aufsetzen
Um den Zugang zu testen ohne dabei den ganzen Ballast der großen Anwendung mitschleppen zu müssen, wird ein neues Projekt zum Testen aufgesetzt.
Das Projekt wird auf Java 8 konfiguriert und kommt mit einer einzigen Abhängigkeit aus: Dem Apache HTTPClient 4.5
Der Code funktioniert, wirft aber noch eine WARNING mit aus:
Mai 30, 2022 4:15:35 PM org.apache.http.client.protocol.RequestTargetAuthentication process
WARNING: NEGOTIATE authentication error: No valid credentials provided (Mechanism level: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt))
200 OK
Response body: [{"Vkorg":"","VkorgDesc":"TEST Korea Limited"}]
Test 2
public static void test02() throws Exception {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(18);
cm.setDefaultMaxPerRoute(6);
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(30000)
.setConnectTimeout(30000)
.setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM))
.setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC))
.build();
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new NTCredentials(username, password, "", ""));
// Finally we instantiate the client. Client is a thread safe object and can be used by several threads at the same time.
// Client can be used for several request. The life span of the client must be equal to the life span of this EJB.
CloseableHttpClient httpclient = HttpClients.custom()
.setConnectionManager(cm)
.setDefaultCredentialsProvider(credentialsProvider)
.setDefaultRequestConfig(requestConfig)
.build();
HttpGet httpGet = new HttpGet(url);
// HttpClientContext is not thread safe, one per request must be created.
HttpClientContext context = HttpClientContext.create();
try ( CloseableHttpResponse response = httpclient.execute(httpGet, context) ) {
StatusLine statusLine = response.getStatusLine();
System.out.println(statusLine.getStatusCode() + " " + statusLine.getReasonPhrase());
String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
System.out.println("Response body: " + responseBody);
}
}
Der Code funktioniert und wirft keine Warnung mehr aus.