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

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.