Nutzungsvorgaben HTTP Invoker: Inhalt

1. Maven-Konfiguration

Zur Verwendung des Bausteins HTTP Invoker genügt es, die folgenden zwei Bibliotheken aus der IsyFact als Maven-Abhängigkeiten einzubinden.

Listing 1. Einbindung des Bausteins HTTP Invoker
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>de.bund.bva.isyfact</groupId>
            <artifactId>isy-serviceapi-core</artifactId>
        </dependency>
        <dependency>
            <groupId>de.bund.bva.isyfact</groupId>
            <artifactId>isy-serviceapi-sst</artifactId>
        </dependency>
    </dependencies>
</dependencyManagement>

2. Realisierung der Service-Schnittstelle

Die folgenden Abschnitte führen durch die Realisierung einer Service-Schnittstelle mit HTTP Invoker. Sie behandeln die folgenden Bestandteile: das Remote-Bean, die Exception-Fassade, die Service-Fassade und schließlich die Transportobjekte. Die letzten Abschnitte beschreiben dann die Integration anderer Bausteine zur Realisierung querschnittlicher Aspekte: Fehlerbehandlung, Logging und die Absicherung von Service-Methoden.

Wichtige ergänzende Hinweise sowie eine sehr umfangreiche Anleitung zur Erstellung von Service-Schnittstellen mit HTTP Invoker bietet das IsyFact Tutorial.

2.1. Realisierung des Remote-Beans

Das Remote-Bean wird als Java-Schnittstelle realisiert. Jede Methode entspricht dabei einem möglichen Service-Aufruf. Jede Methode der RemoteBean-Schnittstelle muss als ersten Parameter ein Objekt der Klasse AufrufKontextTo bzw. ClientAufrufKontextTo verwenden. Dieser Parameter dient dazu, Meta-Informationen zum jeweiligen Aufruf zu übergeben. Daneben enthält die Schnittstelle natürlich noch weitere, fachliche Parameter, die frei definiert werden können.

Die Verwendung von Parametern in einer Schnittstelle ist im folgenden Beispiel dargestellt:

Listing 2. Beispiel für ein Remote-Bean
public class BeispielRemoteBean {
     public BeispielHolenAntwortTo holeBeispielAnfrage(
         AufrufKontextTo kontext, BeispielHolenAnfrageTo anfrage)
         throws BeispielTechnicalToException;
}

Im Folgenden werden die beiden Klassen AufrufKontextTo und ClientAufrufKontextTo näher beschrieben.

AufrufKontextTo: Die Klasse AufrufKontextTo wird für HTTP Invoker Schnittstellen verwendet, die durch IT-Systeme definiert werden. Die Klasse kapselt die Informationen, mit denen andere IT-Systeme aufgerufen wurde:

  • Behörde: Das Behördenkennzeichen der aufrufenden Behörde,

  • Kennung: Die Kennung des aufrufenden Benutzers oder des aufrufenden Fremdprogramms,

  • Kennwort: Das Passwort des aufrufenden Benutzers oder des aufrufenden Fremdprogramms,

  • Rollen: Die Rollen des aufrufenden Benutzers oder des aufrufenden Fremdprogramms,

  • Korrelations-ID: Die ID, um den Service-Aufruf eindeutig zu identifizieren.

ClientAufrufKontextTo: Die Klasse ClientAufrufKontextTo wird für HTTP Invoker Schnittstellen verwendet, die durch Service-Consumer definiert werden. Im Gegensatz zu AufrufKontextTo kapselt diese Klasse die Informationen, um sich bei einem externen Service zu authentifizieren und zu autorisieren:

  • Kennung: Die Kennung mit welcher der externe Service aufgerufen wird,

  • Kennwort: Das Passwort mit welchem der externe Service aufgerufen wird,

  • Zertifikat: Das Zertifikat, um sich beim externen Service zu authentifizieren,

  • Zertifikat-Kennwort: Das Passwort des Zertifikats für die Authentifizierung.

Sowohl AufrufKontextTo als auch ClientAufrufKontextTo sind in der Bibliothek isy-serviceapi-sst definiert.

2.2. Realisierung der Exception-Fassade

Die Exception-Fassade implementiert das Remote-Bean-Interface und definiert in jeder Methode einen try-catch-Block, der alle Fehler des Anwendungskerns abfängt und in Fehler der Service-Schnittstelle umwandelt.

In Listing 3 ist ein Beispiel für eine Exception-Fassade eines IT-Systems angegeben. Die Service-Operationen sind in diesem Fall die Methoden des Interfaces BeispielRemoteBean. Konkret handelt es sich lediglich um die Service-Operation holeBeispielAnfrage. Die Service-Operation ist mit der Annotation @StelltLoggingKontextBereit versehen, die eine mit dem AufrufKontext übergebene Korrelations-ID im Logging-Kontext registriert und diesen beim Verlassen der Methode wieder aufräumt.

Falls im AufrufKontext keine Korrelations-ID vorhanden ist, so erzeugt die Annotation eine neue Korrelations-ID.

Es ist wichtig den Logging-Kontext zu setzen, bevor die Exception-Fassade aktiv wird. Die Implementierung der Service-Operation reicht den Methodenaufruf an die implementierende Klasse (BeispielService) weiter, fängt auftretende Fehler jedoch über einen try-catch-Block ab. Der try-catch-Block unterscheidet zwischen Exceptions der Datenbankzugriffsschicht (DataAccessException) und allen anderen Exceptions (Throwable), um einen passenden Fehlertext in die Log-Dateien zu schreiben.

Listing 3. Beispiel für eine Exception-Fassade
public class BeispielExceptionFassade implements BeispielRemoteBean
{
    private static final IsyLoggerStandard logger = ...;
    private BeispielServiceFassade beispielService;
    ...
    @StelltLoggingKontextBereit
    public BeispielHolenAntwortTo holeBeispielAnfrage
        (AufrufKontextTo kontext, BeispielHolenAnfrageTo anfrage)
        throws BeispielTechnicalToException {
        try {
            return beispielService.holeBeispielAnfrage(kontext,anfrage);
        } catch (DataAccessException e) {
            logger.error("Fehler bei Transaktion", e);
            throw new BeispielTechnicalToException(...);
        } catch (Throwable t) {
            logger.error("...", t);
             throw new BeispielTechnicalToException(...);
        }
    }
    ...
}

2.3. Realisierung der Service-Fassade

Die Service-Fassade übernimmt die restlichen Aufgaben der Service-Logik, d.h. Transformation der Transportobjekte in Objekte des Anwendungskerns und umgekehrt, sowie die Autorisierung des Aufrufs.

In Listing 4 ist ein Beispiel für eine Service-Fassade angegeben. Die Implementierung der Service-Fassade erfolgt analog zur Implementierung der Exception-Fassade. Die nach außen angebotene Service-Operation (holeBeispielAnfrage) wird jedoch nicht eins-zu-eins an die implementierende Klasse weitergeleitet, da sich die Parameter und der Rückgabewert des Aufrufs unterscheiden. Nach außen hin werden Transportobjekte angeboten. Intern arbeitet die Anwendung mit ihren eigenen Entitäten. Diese können sich von den nach außen hin angebotenen Transportobjekten unterscheiden, z.B. weil sie zusätzliche Attribute enthalten, einzelne Attribute anders benennen oder die Daten in irgendeiner Form anders repräsentieren als die Transportobjekte.

In der Service-Fassade erfolgt auch die Autorisierung eines Zugriffs auf eine Service-Methode. Voraussetzung für die Autorisierung ist die Auswertung des mitgelieferten AufrufKontextes über die Annotation @StelltAufrufKontextBereit aus der Bibliothek isy-serviceapi-core an der Service-Methode. Anschließend kann über die Annotation @Gesichert der Bibliothek isy-sicherheit die Berechtigung zum Zugriff auf die Methode geprüft werden. Hier werden alle benötigten Rechte des Aufrufers überprüft. Alternativ kann die Annotation @Gesichert auch an der Service-Klasse verwendet werden, wenn alle Methoden die gleiche Autorisierung erfordern.

Das Mapping im Beispiel wird durch einen Bean Mapper umgesetzt. Vor dem Aufruf werden die Parameter gemappt (Klasse BeispielHolenAnfrageTo auf Klasse BeispielHolenAnfrage), nach dem Aufruf der Rückgabewert (Klasse BeispielHolenAntwort auf Klasse BeispielHolenAntwortTo).

Die Komponente Service-Logik wird durch eine entsprechende Spring-Konfigurationsklasse verschaltet.

Listing 4. Beispiel für eine Service-Fassade
public class BeispielServiceFassade {
    private static final IsyLoggerStandard logger = ...;

    private MapperFacade beanMapper;
    private Beispiel beispiel;

    @StelltAufrufKontextBereit
    @Gesichert(Rechte.RECHT_ZUGRIFFBEISPIEL)
    public BeispielHolenAntwortTo holeBeispielAnfrage(
        AufrufKontextTo kontext, BeispielHolenAnfrageTo anfrage) {

        try {
            BeispielHolenAnfrage anfrageAwk = beanMapper.map(anfrage, BeispielHolenAnfrage.class);
            BeispielHolenAntwort antwortAwk = beispiel.holeBeispielAnfrage(anfrageAwk);

            return beanMapper.map(antwortAwk, BeispielHolenAntwortTo.class);
        } catch (MappingException e) {
            logger.error("...", e);
            throw new TechnicalException(...);
        }
	...
}

2.4. Realisierung von Transportobjekten

Transportobjekte dürfen keine externen Abhängigkeiten haben, da sie Teil der ausgelieferten Schnittstelle sind. Bei Transportobjekten ist zu beachten, dass die UID stets 0 ist (s. Listing 5).

Listing 5. Definition der UID an Transportobjekten
public class BeispielTransportobjekt {
    private static final long serialVersionUID = 0L;
}

2.5. Fehlerbehandlung

HTTP Invoker Schnittstellen besitzen, wie bereits beschrieben, eigene Exceptions, die nur zur Kommunikation mit anderen IT-Systemen dienen. Für diese Transport-Exceptions gilt über die Vorgaben des Konzept Fehlerbehandlung hinaus noch:

  • Sie erben immer von BusinessToException oder TechnicalToException und implementieren somit immer Serializable.

  • Sie stellen die Felder Ausnahme-ID, UUID und Fehlernachricht zur Verfügung.

  • Sie erben nicht von internen Exceptions des IT-Systems.

Daraus ergibt sich für Transport-Exceptions folgende Hierarchie:

fehlerbehandlung010
Abbildung 1. Vererbungshierarchie für Transport-Exceptions

Weiterhin gelten pro Service-Methode folgende Vorgaben:

Definition von technischen Exceptions: Service-Methoden deklarieren keine oder eine technische Exception. Die technische Exception muss für alle Service-Methoden einer Service-Schnittstelle gleich sein.

Definition von fachlichen Exceptions: Service-Methoden können beliebig viele fachliche Exceptions deklarieren. Diese können spezifisch für jede Service-Methode sein.

Übermittlung von Daten: Die Felder Ausnahme-ID, UUID und Fehlernachricht müssen stets übertragen werden. Weiterhin darf kein Stack-Trace übertragen werden.

Die Fehlerbehandlung geschieht in der Exception-Fassade, die einen Service-Aufruf nach außen hin kapselt (siehe Abbildung 2).

fehlerbehandlung011
Abbildung 2. Aufrufkette von der Service-Schnittstelle zum Anwendungskern

Die Exception-Fassade bildet die Klammer um einen Aufruf an die Anwendung und ist für die Top-Level Fehlerbehandlung zuständig. Sie leitet den Aufruf an die Service-Fassade weiter, die wiederum den Anwendungskern aufruft. Dieser zweistufige Prozess ist notwendig, falls es unerwartete Exceptions in der Service-Fassade selbst gibt (z.B. falls diese die Transaktionsklammer um mehrere Aufrufe des Anwendungskerns bildet). Solche unerwartete Exceptions treten außerhalb der eigentlichen Anwendung auf und könnten die Fehlerbehandlung an der Schnittstelle selbst stören. Daher liegt die Exception-Fassade noch vor der Service-Fassade, um auch diese Fehler abzufangen, zu loggen, in Transport-Exceptions umzuwandeln und an den Aufrufer weiterzureichen.

Listing 6 zeigt die Fehlerbehandlung in der Exception-Fassade für das Beispiel-Remote-Bean.

Listing 6. Fehlerbehandlung in der Exception-Fassade
public class BeispielExceptionFassade implements BeispielRemoteBean {
    private static final IsyLoggerStandard LOG = ...;

    private final BeispielServiceFassade service;

    ...

    @StelltLoggingKontextBereit
    public BeispielHolenAntwortTo holeBeispielAnfrage(
        AufrufKontextTo kontext, BeispielHolenAnfrageTo anfrage) {
        throws BeispielBusinessToException, BeispielTechnicalToException {

    try {
        return service.holeBeispielAnfrage(kontext, anfrage);
    } catch (InternalBusinessException ex) {
        LOG.debug("...", ex);
        // Exceptions in Schnittstellen-Exceptions transformieren.
        throw (BeispielBusinessToException)ExceptionMapper.mapException(
                ex, BeispielBusinessToException.class);
    } catch (InternalTechnicalRuntimeException ex) {
        LOG.error("...", ex);
        throw (BeispielTechnicalToException)ExceptionMapper.mapException(
            ex, BeispielTechnicalToException.class);
    } catch (Throwable t) {
        LOG.error("...", t);
        // Unbekannte Exceptions in Schnittstellen-Exceptions transformieren.
        BeispielTechnicalToException ex = ExceptionMapper.createToException(
                AusnahmeIdUtil.getAusnahmeId(t),
                new FehlertextProviderImpl(),
                BeispielTechnicalToException.class);
        LOG.error("Übergebener Fehler: " + ex.getMessage());
        throw ex;
    }
}

Das Code-Beispiel in Listing 6 fängt alle Exceptions und wandelt diese in entsprechende Transport-Exceptions um. Als erwartete Exceptions gibt es hier die Exception InternalBusinessException. Diese wird, sofern sie auftritt, in eine BeispielBusinessToException umgewandelt und weitergereicht.

Zu beachten ist, dass in das Error-Log nur betrieblich relevante Fehler geschrieben werden sollen. Fachliche Fehler sind in der Regel irrelevant für den Betrieb. Daher wird die InternalBusinessException ins Debug-Log geschrieben.

Weitere erwartete Fehler gibt es nicht, somit wird nun eine Fehlerbehandlung für unerwartete Fehler der Anwendung durchgeführt (alle Exceptions vom Typ InternalTechnicalRuntimeException). Als letzte mögliche Fehlerbehandlung werden alle unerwarteten Exceptions vom Typ Throwable gefangen.

Der erste Block in diesem Beispiel behandelt eine fachliche Exception. Die restlichen Blöcke behandeln unerwartete, technische Exceptions. Fachliche Exceptions müssen immer in fachliche Transport-Exceptions umgewandelt werden, alle anderen Exceptions sind in technische Transport-Exceptions umzuwandeln.

Alle Blöcke einer solchen Fassade auf der Anwendungsgrenze verwenden die Klasse ExceptionMapper (siehe Mapping von Exceptions) zur Umwandlung der fachlichen und technischen Exceptions in Transport-Exceptions und zur Erstellung von Transport-Exceptions. Letzteres wird im letzten catch-Block des obigen Code-Beispiels genutzt, da in diesem Fall keine gemäß Konzept Fehlerbehandlung aufgebaute Exception und somit weder Ausnahme-ID, UUID noch Fehlernachricht vorhanden sind. In diesem Fall ist die benötigte Ausnahme-ID zu berechnen, mithilfe der Schnittstelle AusnahmeIdErmittler (siehe Mapping von Exceptions).

Die catch-Blöcke für interne Runtime-Exceptions (hier vom Typ InternalTechnicalRuntimeException) und alle übrigen unerwarteten Exceptions (Throwable) müssen immer implementiert werden. Hierdurch wird verhindert, dass die Schnittstelle nicht spezifizierte Exceptions weiterreicht.

2.5.1. Mapping von Exceptions

Zur Umwandlung von internen Exceptions in Transport-Exceptions stellt die Bibliothek isy-serviceapi-core eine eigene Klasse zur Verfügung: ExceptionMapper (siehe Abbildung 3).

fehlerbehandlung012
Abbildung 3. Methoden der Klasse ExceptionMapper

Die Klasse ExceptionMapper bietet zwei statische Methoden an, um aus fachlichen oder technischen Exceptions entsprechende Transport-Exceptions zu erstellen. Hierfür muss lediglich die umzuwandelnde Exception und die Klasse der gewünschten Transport-Exception mitgegeben werden. Listing 7 zeigt ein Beispiel für die Umwandlung einer technischen Exception in eine technische Transport-Exception.

Listing 7. Mappen einer BaseException in eine technische Transport-Exception
ExceptionMapper.mapException(ex, BeispielTechnicalToException.class)

Es können jedoch weitere Exceptions auftreten, die nicht gemäß Konzept Fehlerbehandlung aufgebaut sind. Diese besitzen keine Ausnahme-ID oder eine UUID, z.B. Runtime-Exceptions aus Frameworks von Drittherstellern. Auch diese Exceptions müssen in Transport-Exceptions umgewandelt werden. Listing 8 zeigt ein Beispiel für eine solche Umwandlung.

Listing 8. Erstellen von Transport-Exceptions
Throwable t = ...;
AusnahmeIdErmittler a = ...;
BeispielTechnicalToException ex = ExceptionMapper.createToException(
                                    a.ermittleAusnahmeId(t),
                                    new FehlertextProviderImpl(),
                                    BeispielTechnicalToException.class);

Dazu muss die Schnittstelle AusnahmeIdErmittler implementiert werden. Sie bietet eine Methode, String getAusnahmeId(Throwable), zur Analyse einer übergebenen Exception und zur Rückgabe der zur Exception passenden Ausnahme-ID. Diese Klasse ist anwendungsspezifisch und für jede Anwendung zu implementieren.

Listing 9 zeigt eine mögliche Implementierung für das Mapping von Exceptions auf Ausnahme-IDs.

Listing 9. Beispielhaftes Mapping von Exceptions auf Ausnahme-IDs
@Bean
public class BeispielAusnahmeIdErmittler implements AusnahmeIdErmittler {

    public String ermittleAusnahmeId(Throwable e) {
        if (throwable instanceof DataAccessException) {
            // generische Datenbank-Fehlermeldung
            return FehlerSchluessel.MSG_GENERISCHER_DB_FEHLER;
        } else if (throwable instanceof JmxException) {
            // generische JMX-Fehlermeldung
            return FehlerSchluessel.MSG_GENERISCHER_JMX_FEHLER;
        } else if (throwable instanceof InternalBusinessException) {
            // Bei Exceptions mit Ausnahme-ID: diese auslesen
            return ((InternalBusinessException) throwable).getAusnahmeID();
        }
        // Kein Mapping Möglich: generische Fehlermeldung
        return FehlerSchluessel.MSG_GENERISCHER_FEHLER;
    }

}

Die Ermittlung der Ausnahme-ID sollte auch auf die internen Exceptions der Anwendung prüfen, auch wenn es nie zu einer positiven Prüfung dieser Bedingungen kommen sollte. Sollte hier also ein Treffer für interne Exceptions auftreten, so wurden die catch-Blöcke nicht sauber implementiert (z.B. wurde einfach nur catch Throwable verwendet). Dies würde dazu führen, dass die Original-Nachricht überschrieben würde, was besonders bei fachlichen Exceptions zu einem Informationsverlust für den Aufrufer führt.

2.6. Absicherung von Service-Methoden

In HTTP Invoker Schnittstellen werden in der Regel einzelne Methoden der Service-Fassade (hinter der Exception-Fassade) abgesichert. Zur Autorisierung von Aufrufen sind Informationen aus dem Aufrufkontext nötig. Dieser wird als erster Schnittstellenparameter in Form eines Transportobjekts mit jedem Aufruf übergeben. Dieses Transportobjekt, eine Instanz der Klasse AufrufKontextTo, muss die Informationen zum anfragenden Anwender und dessen Rollen enthalten.

Zur Autorisierung muss die Service-Methode oder die Service-Fassade mit @StelltAufrufKontextBereit annotiert sein. Die Annotation signalisiert einem Interceptor, das Transportobjekt auszulesen und die bereitgestellten Informationen in den Aufrufkontext des IT-Systems zu übertragen.

Mit dieser Voraussetzung können Service-Methoden durch die Annotation @Gesichert abgesichert werden. Als Standard-Parameter werden alle Rechte aufgelistet, die eine Anfrage für einen Aufruf beinhalten muss. Die Rechte sind immer konjunktiv verknüpft. Eine disjunktive Verknüpfung von Rechten ist nicht möglich.

Folgendes Beispiel (Listing 10) zeigt die Implementierung einer Service-Methode, für die der Aufrufkontext automatisch ausgelesen und anschließend die Autorisierung gegen den Baustein Sicherheit durchgeführt wird.

Listing 10. Absichern einer Service-Methode
@StelltAufrufKontextBereit
@Gesichert("RechtA", "RechtB")
public void abgesicherteMethode(AufrufKontextTo kontext, ...) {
    ...
}

Erfüllt ein Aufrufer die Forderungen der Annotation @Gesichert nicht, so wird ein Fehler des Typs AutorisierungFehlgeschlagenException erzeugt. Der Fehler wird in der Regel nicht lokal behandelt, sondern einfach zurückgegeben. Letztlich deuten fehlende Rechte meist auf einen unberechtigten Zugriff oder einen Fehler in der Konfiguration einer aufrufenden Anwendung hin, sodass hier die normale Fehlerbehandlung greift.

2.7. Kompatibilität zu OAuth 2

Aufgrund der Umstellung auf OAuth 2 zur Authentifizierung und Autorisierung muss das Bearer Token bei jedem Schnittstellenaufruf übertragen werden. Dies geschieht für existierende Schnittstellen transparent.

Der Zugriff auf das Bearer Token geschieht über den AufrufKontextVerwalter. Die Klasse enthält hierfür ein Attribut bearerToken.

Listing 11. Bearer Token in AufrufKontextVerwalter
public interface AufrufKontextVerwalter<T extends AufrufKontext> {

    String getBearerToken();

    void setBearerToken(String bearerToken);

}

Zur Übertragung wird der HTTP-Header Authorization verwendet.

3. Paketierung einer Service-Schnittstelle

IT-Systeme teilen sich folgende Java-Klassen der HTTP Invoker Schnittstelle:

  • Java-Interface der Schnittstelle (Remote-Bean),

  • Java-Klassen der Transportobjekte,

  • Java-Klassen der Transport-Exceptions.

Diese Klassen müssen als JAR mit einer einzigen Abhängigkeit auf die Bibliothek isy-serviceapi-sst paketiert werden. Sie werden anhand der Vorgaben in Detailkonzept Komponente Service und IsyFact Versionierung versioniert und anhand der Vorgaben in IsyFact Namenskonventionen benannt.

4. Bereitstellung einer Service-Schnittstelle

In der Konfigurationsklasse der Service-Schicht wird die HTTP Invoker Konfiguration der Service-Schnittstelle eingebunden. Dazu werden das Remote-Bean-Interface und die zugehörige Implementierung in Form der Exception-Fassade als Service konfiguriert Listing 12. Der Bean-Name ist für die URL, unter welcher der Service erreichbar sein wird, wichtig.

Listing 12. Konfiguration von Remote Bean und Exception Fassade als Service
@Configuration
public class ServiceConfiguration {

    @Bean("/BeispielBean_v1_0")
    public HttpInvokerServiceExporter beispielService(BeispielExceptionFassade beispiel) {
        HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
        exporter.setService(beispiel);
        exporter.setServiceInterface(BeispielRemoteBean.class);
        return exporter;
    }
}

5. Nutzung einer Service-Schnittstelle

Zur Nutzung einer entfernten Schnittstelle bindet ein IT-System das JAR der Schnittstelle via Maven ein und initialisiert die Remote-Beans damit. Das geschieht über die vom Spring Framework bereitgestellte Factory-Klasse HttpInvokerProxyFactoryBean, wie in Listing 13 dargestellt. Auf dieser Bean können dann die entfernten Methoden aufgerufen werden.

Listing 13. Konfiguration für die Nutzung einer entfernten Schnittstelle
@Configuration
public class ServiceConfiguration {
    @Bean
    public HttpInvokerProxyFactoryBean beispielRemoteBean(HttpInvokerRequestExecutor executor, ServiceConfigProperties config) {
        HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean();
        invoker.setServiceUrl(config.getServiceUrl());
        invoker.setServiceInterface(BeispielRemoteBean.class);
        invoker.setHttpInvokerRequestExecutor(executor);
        return invoker;
    }

    @Bean
    public TimeoutWiederholungHttpInvokerRequestExecutor executor(ServiceConfigProperties config) {
        TimeoutWiederholungHttpInvokerRequestExecutor executor = new TimeoutWiederholungHttpInvokerRequestExecutor();
        executor.setAnzahlWiederholungen(config.getWiederholungen());
        executor.setTimeout(config.getTimeout());
        return executor;
    }
}

Die FactoryBean erwartet eine Service-URL und ein Remote-Bean-Interface zur Initialisierung. Der Host-Teil der URL muss in jedem Fall in der betrieblichen Konfiguration der Anwendung zu finden sein. Das Remote-Bean-Interface ist im Schnittstellen-JAR verfügbar.

Die Nutzung des hier im Beispiel verwendeten TimeoutWiederholungHttpInvokerRequestExecutor ist zwar optional, wird aber dringend empfohlen, um Langläufer zu vermeiden. Dieser Executor bricht nach dem angegebenen Timeout die Anfrage ab und wiederholt sie bis zur maximalen angegebenen Wiederholungsanzahl. Die Parameter für Timeout und Wiederholungen sind Bestandteil der betrieblichen Konfiguration (s. Konzept Konfiguration).

Nutzt das IT-System den Baustein Logging, muss statt der Spring-eigenen Factory die erweiterte IsyHttpInvokerProxyFactoryBean genutzt werden. Sie versieht die Remote-Beans automatisch mit einem LogMethodInterceptor, der die Aufrufzeiten der ausgehenden Aufrufe misst und loggt. Die Konfiguration erfolgt wie in Listing 14 gezeigt.

Listing 14. Konfiguration mit IsyHttpInvokerProxyFactoryBean
@Configuration
public class ServiceConfiguration {
    @Bean
    public IsyHttpInvokerProxyFactoryBean beispielRemoteBean(HttpInvokerRequestExecutor executor, ServiceConfigProperties config) {
        IsyHttpInvokerProxyFactoryBean invoker = new IsyHttpInvokerProxyFactoryBean();
        invoker.setServiceUrl(config.getServiceUrl());
        invoker.setServiceInterface(BeispielRemoteBean.class);
        invoker.setHttpInvokerRequestExecutor(executor);
        invoker.setRemoteSystemName(config.getRemoteSystemName());
        return invoker;
    }
}

Die erweiterte FactoryBean erwartet nur einen zusätzlichen Parameter remoteSystemName. Dieser wird genutzt, um in allen Log-Ausgaben einen sprechenden Systemnamen zu setzen.

5.1. Kompatibilität zu IF1/IF2/IF3

5.1.1. IF1- und IF2-Client

Anwendungen basierend auf der IF1 oder IF2 können HTTP-Invoker-Aufrufe wie gewohnt durchführen, da diese einen gefüllten AufrufKontext besitzen. Hier bleibt das Vorgehen wie bisher in der Anleitung beschrieben.

5.1.2. IF3-Client

Im Zuge des Upgrades zu IF3 wird der AufrufKontext (deprecated) nicht mehr verwendet und der von IF3-Anwendungen verwendete Sicherheitsbaustein isy-security wird nativ keine HTTP-Invoker Schnittstellen mehr unterstützen. Um weiterhin die Kompatibilität von HTTP-Invoker-Aufrufen gegenüber IF1-, IF2- und IF3-Anwendungen zu gewährleisten, wird bei einem HTTP-Invoker-Aufruf ein AufrufKontext als Transportobjekt ad hoc erzeugt.

IF3 calls.dn
Abbildung 4. Überblick eines IF3-Aufrufes

Das AufrufKontextTo wird dabei direkt aus dem Spring SecurityContext bzw. dem AccessToken erzeugt. Dazu wird der Berechtigungsmanager des Bausteins isy-security verwendet; isy-security ist als eine provided-Dependency im isy-serviceapi-core-Baustein eingebunden, d.h. für IF1 und IF2-Anwendungen wird diese Dependency nicht benötigt, da diese Anwendungen bereits einen gefüllten AufrufKontext vorhalten und für eine IF3-Anwendung muss diese Dependency zur Runtime zur Verfügung stehen, wenn ein Transportobjekt des AufrufKontext ad hoc erzeugt werden soll. In der IF3-Applikation muss somit isy-security eingebunden sein, um ein AufrufKontextTo zu erzeugen.

Zur Erzeugung des AufrufKontextTo wird eine RemoteInvocationFactory eingesetzt, welche beim Aufruf einer Servicemethode durch den Parameter null (statt einem AufrufKontextTo Objekt) signalisiert bekommt, dass eine Erstellung notwendig ist.

@Autowired
TestService testServiceIsy;

[...]

// Parameter "null" -> AufrufKontextTo wird ad-hoc aus SecurityContext erstellt
testServiceIsy.holeDenText(null);

Per Default wird die RemoteInvocationFactory nur in IsyHttpInvokerProxyFactoryBean verwendet. Falls die Spring-eigene Factory verwendet wird, muss die RemoteInvocationFactory explizit in einem Setter (setRemoteInvocationFactory()) gesetzt werden.

6. Sicherheit

Bei der Übertragung von Daten über eine HTTP-Invoker-Schnittstelle werden Java-Klassen serialisiert und deserialisiert. Wird bei der Deserialisierung die Integrität der Daten nicht sichergestellt, können Angreifer schadhaften Code über eine HTTP-Invoker-Schnittstelle einschleusen (siehe CWE-502). Das Betrifft alle HTTP-Invoker-Schnittstellen, die direkt von außerhalb einer Anwendungslandschaft benutzt werden können.

Um die Sicherheit an dieser Stelle zu erhöhen, müssen HTTP-Invoker-Schnittstellen den IsyHttpInvokerServiceExporter verwenden. Dieser Exporter deaktiviert standardmäßig die Verwendung von Proxy-Klassen, über welche potenziell schadhafter Code deserialisiert werden kann.