An diesem Beispiel soll die Erstellung einer OWSM (Oracle Web Services Manager) Custom Assertion vorgestellt werden. Die Custom Assertions erlauben es, nahezu beliebige Policies zu erstellen, die wie die von Oracle vorgefertigten Policies auf Service-Endpunkte, SOA Komponenten oder auch Service-Referenzen angewendet werden können. Typischerweise kümmern sich diese Policies um Authentifizierung und Autorisierung.

Unser Anwendungsfall: wir möchten einen bestimmten Service nur für bestimmte IP-Adressen zugreifbar machen. Die Vorstellung ist, dass wir eine Liste mit gültigen IP-Adressen für den jeweiligen Service konfigurieren. Alle Requests, die von anderen Adressen kommen, sollen abgelehnt werden.

Diese Funktionalität ist in der SOA Suite leider nicht Out-Of-The-Box vorhanden.

1. Projekt-Setup

Um das Projekt aufzusetzen wird in JDeveloper einfach ein Standard-Java-Projekt erstellt.

Bevor die Entwicklung starten kann, müssen einige Bibliotheken für OWSM und SOA in den Build-Pfad eingebunden werden. Die genaue Beschreibung spare ich mir an dieser Stelle – wer möchte kann jederzeit die aktuelle Liste aus dem fertigen Projekt (siehe unten) kopieren (Tipp: Einfach die .jpr-Datei mit einem Texteditor bearbeiten und sämtliche Library References rüberkopieren – nach einem Refresh in JDev, sind die Bibliotheken dort bekannt).

2. Grundstruktur der Assertion-Klasse

Grundsätzlich ist eine Custom Assertion lediglich eine einzige Klasse, die von oracle.wsm.policyengine.impl.AssertionExecutor erbt. Hier ist unser erstes Grundgerüst:

[code language=“java“]
public class ClientIpAssertion extends AssertionExecutor {
private final static Logger LOG = Logger.getLogger(ClientIpAssertion.class.getName());

protected IAssertion mAssertion = null;
protected IExecutionContext mEcontext = null;
protected oracle.wsm.common.sdk.IContext mIcontext = null;

public IResult execute(IContext iContext) {

// Hier wird die Assertion ausgefuehrt
}

public void init(IAssertion iAssertion, IExecutionContext iExecutionContext, IContext iContext) {
mAssertion = iAssertion;
mEcontext = iExecutionContext;
mIcontext = iContext;
}

public void destroy() {
}
}
[/code]

Die Methode init() wird vom OWSM zum Initialisieren aufgerufen. Hier kommen schonmal grundsätzlich einige Umgebungsobjekte mit, die später noch wichtig sind, um z.B. die Parameter auszulesen.

Die Methode destroy() wird beim Zerstören der Klasse aufgerufen. Falls man irgendwelche Ressourcen belegt hat, kann man diese hierin freigeben.

3. Metadaten

Neben der Implementierung der Assertion benötigt man noch eine XML-Datei (ClientIpAssertion.xml) mit einigen Metadaten zur Assertion.

[code language=“xml“]



com.opitzconsulting.clientipassertion.ClientIpAssertion




127.0.0.1






[/code]

Hierin wird z.B. die ID der Assertion festgelegt (hier: „ClientIp“), woran die Policy angehängt werden kann (hier: „generic“, sprich kann überall verwendet werden – man könnte es auch auf Service-Endpunkte, SOA Komponenten oder SOA Referenzen einschränken) und weitere allgemeine Infos wie ein Anzeigename, Beschreibung, usw.

Im Tag „orawsp:Implementation“ wird der vollständige Name der Implementierungsklasse angegeben. Besonders interessant ist der nächste Abschnitt, der die möglichen Konfigurationsparameter der Assertion zusammen mit Standardwerten enthält. Hier: Ein Parameter „allowedIpAddresses“ vom Typ „string“ mit dem Standardwert „127.0.0.1“.

Diese Konfigurationsparameter können später im Enterprise Manager konfiguriert werden, wenn man sich aus den Assertions eine eigene Policy zusammenbaut (siehe Deployment).

4. IP-Vergleich

Kommen wir zum Kern der Assertion: Vergleich der Client IP Addresse mit einer Liste von erlaubten IP-Addressen. Da dieser Vergleich für jeden Aufruf ausgeführt werden muss, erfolgt die Implementierung vollständig in der execute()-Methode.

[code language=“java“]
public IResult execute(IContext iContext) {
IResult result = new Result();
result.setStatus(IResult.FAILED);

// Abschnitt A
IMessageContext.STAGE stage = ((IMessageContext)iContext).getStage();
LOG.fine(„stage=“ + stage);
if (stage != IMessageContext.STAGE.request) {
LOG.fine(„Nothing to process on this stage“);
result.setStatus(IResult.SUCCEEDED);
return result;
}

// Abschnitt B
SOAPBindingMessageContext soapbindingmessagecontext = (SOAPBindingMessageContext)iContext;

String remoteAddress = soapbindingmessagecontext.getRemoteAddr();
String[] allowedIpAddresses = new String[] { „127.0.0.1“ };

boolean clientIpValid = false;
for (String allowedIpAddress : allowedIpAddresses) {
if (allowedIpAddress.equals(remoteAddress)) {
clientIpValid = true;
}
}

// Abschnitt C
if (clientIpValid) {
result.setStatus(IResult.SUCCEEDED);
} else {
WSMException exception = new WSMException(„Client IP address “ + remoteAddress + “ is not allowed“);
result.setFault(exception);
result.setStatus(IResult.FAILED);
}

return result;
}
[/code]

Als allererstes initialisieren wir ein IResult-Objekt mit einem Fehlerstatus – per Default soll ein Fehler zurückkommen.

Im Abschnitt A geht es zunächst darum, zu prüfen, in welchem Stage wir uns befinden. Die Policy wird grundsätzlich für Request und Reply ausgeführt. Da für unsere Policy nur der Request Stage interessant ist geben wir für alle anderen Fälle ein SUCCEEDED zurück und beenden an dieser Stelle die Bearbeitung.

In Abschnitt B holen wir uns zuerst die Remote IP Addresse aus dem SOAPBindingMessageContext. Hinweis: Es könnte auch sein, dass hier kein SOAPBindingMessageContext vorliegt, falls der Aufruf nicht über ein SOAP Binding erfolgt, sondern z.B. über ein Direct Binding. Dieser Fall wird hier vernachlässigt.

Dann legen wir – vorerst hardcodiert – ein Array mit gültigen IP-Adressen ein. Trifft der Vergleich einer IP-Adresse dieser Liste mit der Remote-Adresse zu, so wird die Variable clientIpValid auf true gesetzt.

In Abschnitt C wird ein entsprechendes Result gesetzt auf Basis von clientIpValid. Interessant ist hier vor allem der Fehlerfall. Hier sollte man eine brauchbare Fehlerbeschreibung eintragen, da diese bei Fehlschlag an den Aufrufer zurückgesandt wird. Hinweis: Es gibt keinen Flow Trace und keine Composite Instance, falls eine der eingehenden Policies fehlschlägt – dadurch wird diese Fehlermeldung doppelt wichtig, wenn man nicht lange Zeit Logs durchwühlen möchte.

Die Assertion kann nun deployed werden und wird nur noch Requests von 127.0.0.1 durchlassen.

5. Parameter übergeben

Natürlich wollen wir die IP-Adressen nicht hardcodieren, vor allem, weil man dann für jede Ü„nderung der IP-Adressen die komplette SOA Suite neustarten müsste – außerdem kann man die Assertion dann nicht wiederverwenden.

Wir haben den Parameter in den Metadaten oben bereits definiert. Nun brauchen wir also noch eine Möglichkeit, diesen in der Assertion auszulesen. Dazu verwenden wir folgendes Code-Schnipsel:

[code language=“java“]
private Properties configProps = new Properties();

private void retrieveConfiguration() {
IAssertionBindings bindings = ((SimpleAssertion)(mAssertion)).getBindings();
if (bindings != null) {
List cfgl = bindings.getConfigs();
if (!cfgl.isEmpty()) {
IConfig cfg = cfgl.get(0);
List configProperties = cfg.getProperties();
if (configProperties != null) {
for (IProperty configProperty : configProperties) {
String propName = configProperty.getName();
String propValue = configProperty.getValue();
if (propValue == null || propValue.trim().isEmpty())
propValue = configProperty.getDefaultValue();
if (propValue != null)
configProps.setProperty(propName, propValue);
}
}
}
}
}

public void init(IAssertion iAssertion, IExecutionContext iExecutionContext, IContext iContext) {
mAssertion = iAssertion;
mEcontext = iExecutionContext;
mIcontext = iContext;
retrieveConfiguration(); // Dieser Aufruf wurde hinzugefügt
}
[/code]

Diese Methode sollte am Besten aus der init()-Methode aufgerufen werden, da OWSM die init()-Methode immer aufrufen wird, wenn eine Ü„nderung der Konfiguration erfolgt und sich somit die Policyversion erhöht.

Nun stehen alle Properties in dem privaten Property-Objekt configProps und können von dort verwendet werden. Da wir davon ausgehen, dass hier eine „,“-separierte Liste mit IP-Adressen drin steht, ersetzen wir unsere zuvor hardcodierte Liste durch folgendes:

[code language=“java“]
String[] allowedIpAddresses =
ScenarioUtils.getConfigPropertyValue(
„allowedIpAddresses“,
soapbindingmessagecontext,
configProps,
null)
.split(„,“);
[/code]

Und schon wird die konfigurierte Liste an erlaubten Adressen für die Prüfung herangezogen.

6. Deployment

Das Deployment erfolgt mit den folgenden Schritten:

  • JAR mit der Assertion in DOMAIN_HOME/lib kopieren
  • SOA Suite neustarten
  • Im EM unter Weblogic-Domain -> Web Services -> Policies -> Assertion Templates die Assertion importieren (ClientIpAssertion.xml)
  • Im EM unter Weblogic-Domain -> Web Services -> Policies eine neue Policy erstellen
  • In die Policy die Assertion „custom/ClientIp“ einfügen und unten unter „Assertion-Inhalt“ die Liste mit IP-Addressen vom Standard-Wert aus abändern

Nun kann die Policy analog anderer Sicherheitspolicies an Service-Endpunkte angehängt werden, die dann nur noch von bestimmten IP-Adressen aus aufgerufen werden können.

Zusammenfassung

Die Entwicklung eigener Policies ist immer dann hilfreich, wenn man in der SOA Suite eine bestimmte Anforderung in Bezug auf Security umsetzen möchte, die nicht Out-Of-The-Box geboten ist. Auf diese Weise haben wir beispielsweise auch eine Weitergabe der Authentifizierung im Payload realisiert, so dass unsere BPEL-(Geschäfts-)Prozesse frei von Authentifizierungsdetails bleiben.

Das komplette Projekt mit der oben erstellen Policy gibt es hier zum Download:
ClientIPAssertion.zip

1 Kommentar

Antwort an phone edmonton Antwort verwerfen