Mein Icon

SAP On-Premise Integration über BTP mit Node.js, SAP Cloud SDK & XSUAA: Ein Praxisprojekt

28. Juni 2025·8 Minuten zu lesen
on-premise-integration-nodejs-sap-cloud-sdk-xsuaa-btp

Einleitung

In diesem Beitrag zeigen wir, wie man einen Node.js-basierten Proxy-Service für die SAP BTP entwickelt, der On-Premise-Systeme sicher über SAP Cloud SDK, XSUAA und Connectivity Service anbindet.


Ziel des Projekts

Ziel ist es, einen flexiblen Proxy bereitzustellen, der REST-Anfragen aus der Cloud an On-Premise-SAP-Systeme weiterleitet. Die Authentifizierung und Autorisierung erfolgen über XSUAA, die Konnektivität über den SAP Connectivity Service.


Architektur & Komponenten

  • Node.js/Express: Webserver und Routing
  • SAP Cloud SDK: HTTP-Requests & Destination Handling
  • XSUAA & Passport: Authentifizierung/Autorisierung via JWT
  • Connectivity Service: Sichere Verbindung ins On-Premise-Netz
  • xs-app.json: Zentrale Routing-Konfiguration

Implementierungsschritte

1. Projektstruktur & Abhängigkeiten

Initialisiere das Projekt und installiere die benötigten Pakete:

npm init -y
npm install express passport @sap/xssec @sap/xsenv @sap-cloud-sdk/connectivity @sap-cloud-sdk/http-client

2. Authentifizierung mit XSUAA

xs-security.json

{
  "xsappname": "on-premise-proxy-app",
  "tenant-mode": "dedicated",
  "oauth2-configuration": {
    "credential-types": ["binding-secret"],
    "redirect-uris": [
      "https://<your-subdomain>.cfapps.<region>.hana.ondemand.com/**"
    ]
  },
  "scopes": [
    {
      "name": "$XSAPPNAME.read",
      "grant-as-authority-to-apps": [
        "$XSAPPNAME(application,read-client)"
      ]
    },
    {
      "name": "$XSAPPNAME.edit",
      "grant-as-authority-to-apps": [
        "$XSAPPNAME(application,edit-client)"
      ]
    }
  ],
  "role-templates": [
    {
      "name": "OnPremiseProxyReader",
      "default-role-name": "OnPremiseProxyReader",
      "scope-references": ["$XSAPPNAME.read"]
    },
    {
      "name": "OnPremiseProxyEditor",
      "default-role-name": "OnPremiseProxyEditor",
      "scope-references": ["$XSAPPNAME.edit"]
    }
  ]
}

Passport/XSUAA-Integration (server.js)

const express = require("express");
const passport = require("passport");
const xsenv = require("@sap/xsenv");
const { JWTStrategy } = require("@sap/xssec").v3;

xsenv.loadEnv();
const xsuaaService = xsenv.getServices({ xsuaa: { tag: "xsuaa" } });
const jwtStrategy = new JWTStrategy(xsuaaService.xsuaa);

passport.use(jwtStrategy);
const app = express();

app.use(passport.initialize());
app.use(passport.authenticate("JWT", { session: false }));

3. Routing & Proxy-Logik

xs-app.json

{
  "routes": [
    {
      "source": "/api/backend/getItems",
      "target": "/sap/bc/rest/zservice/items?sap-client=100",
      "destination": "GENERIC_BACKEND",
      "csrfProtection": false,
      "httpMethods": ["GET"],
      "scopes": ["$XSAPPNAME.read"]
    },
    {
      "source": "/api/backend/postItems",
      "target": "/sap/bc/rest/zservice/itemdata",
      "destination": "GENERIC_BACKEND",
      "csrfProtection": false,
      "httpMethods": ["POST"],
      "scopes": ["$XSAPPNAME.edit"]
    }
  ]
}

Dynamisches Routing (server.js)

const fs = require("fs");
const path = require("path");
const { getDestination } = require("@sap-cloud-sdk/connectivity");
const { executeHttpRequest } = require("@sap-cloud-sdk/http-client");

const xsAppConfigPath = path.join(__dirname, "xs-app.json");
const xsAppConfig = JSON.parse(fs.readFileSync(xsAppConfigPath, "utf8"));

xsAppConfig.routes.forEach((route) => {
  route.httpMethods.forEach((method) => {
    app[method.toLowerCase()](route.source, async (req, res) => {
      const requiredScope = route.scopes[0].replace(
        "$XSAPPNAME",
        xsuaaService.xsuaa.xsappname
      );
      if (!req.authInfo.checkScope(requiredScope)) {
        return res.status(403).json({ error: "Unauthorized" });
      }

      const userJwt = req.authInfo.getTokenInfo().getTokenValue();
      const destination = await getDestination({
        destinationName: route.destination,
        jwt: userJwt,
      });
      if (!destination) {
        return res.status(500).send("Destination not found");
      }

      try {
        const response = await executeHttpRequest(
          destination,
          {
            method: method,
            url: route.target,
            headers: { "Content-Type": "application/json" },
            data: req.body,
          },
          { fetchCsrfToken: route.csrfProtection }
        );
        res.status(response.status).send(response.data);
      } catch (error) {
        res.status(500).json({ error: error.message });
      }
    });
  });
});

4. Zugriff auf On-Premise-Systeme

Destination-Konfiguration (BTP Cockpit)

  • Name: GENERIC_BACKEND
  • Type: HTTP
  • URL: http://:
  • Proxy Type: OnPremise
  • Authentication: BasicAuthentication (oder wie benötigt)
  • Properties: sap-client, falls erforderlich

mta.yaml (Ausschnitt)

resources:
  - name: proxy-destination
    type: org.cloudfoundry.managed-service
    parameters:
      service: destination
      service-plan: lite
  - name: proxy-connectivity
    type: org.cloudfoundry.managed-service
    parameters:
      service: connectivity
      service-plan: lite

5. Lokale Entwicklung & Test

default-env.json (Beispiel)

Diese Informationen kannst du nach dem ersten Deployment der Anwendung ganz einfach in den Environment Variables der Applikation kopieren. Jedoch musst die die beiden Werte onpremise_proxy_host und onpremise_proxy_http_port anpassen. Das kannst du hier nachlesen.

{
  "VCAP_SERVICES": {
    "xsuaa": [ /* ... */ ],
    "destination": [ /* ... */ ],
    "connectivity": [ 
      {"credentials": {
          "onpremise_proxy_host": "localhost", // Hier ändern
          "onpremise_proxy_http_port": "8081"
      },
        /* ...*/}
     ]
  }
}

SSH-Tunnel für Connectivity Proxy

cf ssh on-premise-proxy -L localhost:8081:connectivityproxy.internal.cf.<region>.hana.ondemand.com:20003

SSH-Fehlerbehebung

Falls beim Aufbau des SSH-Tunnels folgender Fehler auftritt:

Error opening SSH connection: You are not authorized to perform the requested action.

Stelle sicher, dass SSH für die Anwendung aktiviert ist. Führe dazu folgende Befehle aus:

cf enable-ssh on-premise-proxy
cf restart on-premise-proxy

Anschließend sollte der SSH-Tunnel wie beschrieben funktionieren.

Server starten

npm run test

6. Deployment auf SAP BTP

mbt build -p cf
cf deploy ./mta_archives/on-premise-proxy_1.0.0.mtar --strategy blue-green --skip-testing-phase

manifest.yml (Ausschnitt)

applications:
  - name: on-premise-proxy
    path: .
    random-route: true
    instances: 1
    memory: 256M
    buildpacks:
      - nodejs_buildpack
    services:
      - proxy-xsuaa
      - proxy-destination
      - proxy-connectivity

Fazit

Mit dieser Architektur lässt sich ein sicherer, flexibler Proxy-Service für SAP BTP realisieren, der On-Premise-Systeme nahtlos integriert und moderne Authentifizierungsmechanismen nutzt. Die modulare Struktur erlaubt eine einfache Erweiterung und Anpassung an individuelle Anforderungen.

© Felix Stiffel 2025 | Alle Rechte vorbehalten