Aktuelles

BSP Anmeldung 2.0

Webdynpro und Fiori zum Trotz haben SAP Business Server Pages (BSP) einen entscheidenden Vorteil. Wer ausserhalb der SAP Welt Webseiten mit PHP und Javascript entwickelt, findet sich ganz besonders schnell zurecht. Man kann alle künstlerischen Freiheiten nutzen und mit wenigen Handgriffen beliebige Daten aus einem SAP System aufbereiten. Einzig das Design eines ansprechenden Anmeldebildschirms erfordert einige technische Kniffe – sieht der Standard doch sehr altbacken aus.

Auf der Suche nach Alternativen sprach mich der Calm breeze login Screen ganz besonders an. Mit nur 3 Zeilen Javascript und der Verwendung von CSS Animationstechnik macht sich schon vor dem Start einer Applikation das Gefühl breit, dass man mit Liebe zum Detail gearbeitet hat. Hat man bisher nur mit die SAP eigenen Anmeldemasken genutzt, gestaltet sich die Integration in den ABAP Applikationsserver sehr mühsam. Man muss verstehen, dass die darzustellenden Seiten zwar als individuelles HTML an den Browser ausgeliefert werden, aber auf Serverseite nur mittels String Konkatenation erzeugt werden können. Zuerst gilt es den technischen Rahmen zu schaffen. Dafür ist eine eigene Klasse als Ableitung von CL_ICF_SYSTEM_LOGIN anzulegen. Für den individuellen Anmeldedialog müssen drei Methoden redefiniert werden.

  • HTM_LOGIN: Rendert die normale Anmeldeseite. Diese Methode wird auch dann aufgerufen, wenn am Client falsche Anmeldedaten eingegeben wurden.
  • HTM_CHANGE_PASSWD: Methode, die normalerweise zum Rendern des Passwort Änderungsdialogs verwendet wird. Sie wird in diesem Beispiel in abgewandelter Form verwendet.
  • HTM_MESSAGE_BOX: Methode, die einen Infodialog erzeugt, wenn das Passwort erfolgreich geändert wurde. Auch hier wird eine spezielle Anpassung verwendet. 

In der normalen SAP Logik rendert HTM_LOGIN eine Webseite mit einem Passwort Formular, das mittels <button type=submit> an den Applikationsserver übertragen wird. Die SAP eigenen ICF Funktionen prüfen die Richtigkeit und leiten den Anwender an die BSP Applikation weiter oder springen im Fehlerfall zurück auf eine der drei Dialoge. Unschön dabei ist, dass die Seite bei Fehleingaben im Browser neu aufgebaut werden muss.

In den Zeiten von Ajax und XMLHttpRequests (XHR) kann man das schöner gestalten. In der Praxis wird die zugrundeliegende Logik über eine Javascript Bibliothek abstrahiert. Das vereinfacht die Entwicklung und befreit von individuellen Spezialitäten älterer Browser. Zwei bekannte und weitestgehend kompatible Bibliotheken sind JQuery und Zepto.js. Für unseren Fall reicht die kleinere Zepto.js Bibliothek, die als MIME in den Applikationsserver importiert wird.

 
Leider können MIMEs nicht direkt mittels URL Verknüpfung <script src="/..."> auf einem Login Bildschirm dargestellt werden. Sie laufen im Kontext der Applikation und erfordern daher eine Anmeldung, bevor sie am Client geladen werden. Um sie trotzdem auf der Anmeldeseite zu integrieren, nutzen wir einen selbstgeschriebenen Loader, der den Inhalt auf der Loginseite integriert. Dieser liest den Inhalt des MIMEs und konvertiert die binären Daten in den ASCII Ursprungszustand. Mit Aufruf der folgenden BUILD_ZEPTO Methode steht im Ergebnisstring RV_HTML der Inhalt der Zepto.js Bibliothek. 

method BUILD_ZEPTO.
data: fileinfos type standard table of SDOKFILACI,
      fileinfo  type SDOKFILACI,
      bindata   type SDOKCNTBINS,
      i         type i,
      mimedata  type SKWF_IO.
mimedata-OBJTYPE = 'P'. mimedata-CLASS   = 'M_TEXT_P'. mimedata-OBJID   = '403380567B3F5F5AE10000000AFE552C'. call function 'SKWF_PHIO_LOAD_CONTENT'   exporting phio = mimedata   tables    FILE_CONTENT_BINARY = bindata              FILE_ACCESS_INFO = fileinfos. read table fileinfos into fileinfo index 1. i = fileinfo-FILE_SIZE. call function 'SCMS_BINARY_TO_STRING'   exporting INPUT_LENGTH = i   importing TEXT_BUFFER = rv_html   tables BINARY_TAB = bindata.

Die zweite Herausforderung ist die sinnvolle Verarbeitung von XHR Requests auf der Loginseite. Man bedenke, dass der Anwender im gesamten Dialog noch nicht am SAP System angemeldet ist. Daher kann immer wieder nur die gleiche Loginmethode aufgerufen werden. Man kann sich in diesem Kontext die Variablen M_SAPRC und M_SUBRC zu Nutze machen und je nach deren Inhalt eine unterschiedliches Ergebnis auf der Seite präsentieren. Ein Beispiel:

  • Erster Aufruf Loginseite (Methode HTM_LOGIN): Kompletter Seiteninhalt wird gerendert
  • Benutzer gibt Daten für einen gesperrten Account an.
  • (Zweiter) XHR Aufruf der Loginseite: Das ICF Framework setzt M_SUBRC = 2. Es braucht nur  der Inhalt „LoginFailedAccountLocked“ als Antwort zurückgegeben werden.
  • Der Browser wertet die Antwort per Javascript aus und präsentiert die Meldung „Account gesperrt“.

Die Implementierung in der HTM_LOGIN Methode ist trivial. Sie wird der normalen Verarbeitung vorangestellt und beendet die Methode vorzeitig.

method HTM_LOGIN.
…
if m_saprc = 0 and m_subrc = 2.
  Rv_html = '/* LoginFailedAccountLocked */'.
  return.
endif.
…

Die letzte Herausforderung ist der Umgang mit Eingabefeldern. Neben sap-client, sap-user und sap-password gibt es eine Reihe von versteckten Feldern, die im ICF Framwork generiert werden und beim Absenden für die Folgeverarbeitung relevant sind. Besonderes Augenmerk liegt auf dem Feld sap-login-XSRF. Darüber realisiert der SAP Applikationsserver einen Schutz, um unkontrollierte Anmeldungen zu vermeiden. Siehe Cross-Site-Request-Forgery bei Wikipedia. Zur Sicherstellung dass Anmeldedialog und Antwort vom Browser zusammenpassen generiert SAP ein eindeutige ID im Feld sap-login-XSRF. Diese muss in der Antwort des Browsers wieder enthalten sein. Nur so wird sichergestellt, dass es sich um eine valide Anmeldung handelt.

Im Zusammenhang mit Ajax XHR Requests müssen diese Informationen bei jedem Datenaustausch vom Server in die Antwort integriert werden und am Client mittels Javascript wieder in die entsprechenden Eingabefelder übertragen werden. Hierzu wird ein Wrapper für die bereits existierende Methode BUILD_HIDDEN_FIELDS() benötigt.

method BUILD_XFIELDS.
data: hff type string,
      ffs type TIHTTPNVP,
      i   type i,
      s   type string,
      ff  type IHTTPNVP.

i = 10. hff = ''. ffs = BUILD_HIDDEN_FIELDS( ). loop at ffs into ff.   s = i. condense s. concatenate 'HFF' s into s. i = i + 1.   concatenate hff '<input type="hidden" id="'               s '" name="' ff-name '" value="'               ff-value '">' co_crlf into hff. endloop. rv_html = hff. endmethod.

Die korrespondierende Methode im Javascript auf Clientseite in Zepto.js (bzw. JQuery) Notation zum Auslesen der XHR Antwort (Inhalt in der Variablen data):

for (i=10;i<MAX_HFF ;i++) {
  ss = 'HFF' + i;
  p=data.indexOf(ss);
  if (p >= 0) { 
    s=data.substring(p+13,p+300);
    p=s.indexOf('"');
    n=s.substring(0,p);
    s=s.substring(p+9); 
    p=s.indexOf('"');
    v=s.substring(0,p);
    $('#' + ss).val(v);
    $('#' + ss).attr('name', n);
  }
}

Mit der Logik genügt es den Anmeldedialog nur einmalig darzustellen und in allen folgenden Anfragen die verkürzte Pseudo HTML Antwort der Anmeldeseite auszulesen. Die Betrachtung der vielen kleinen anderen Details zur Darstellung der Anmeldeseite würden den Beitrag sprengen. Aus diesem Grunde anbei die Inhalte der Anmeldeklasse Z_CCMX_CL_LOGIN.

  • Public Section
  • Private Section
  • Protected Section
  • Methode HTM_CHANGE_PASSWD
  • Methode HTM_LOGIN
  • Methode HTM_MESSAGE_BOX
  • Methode BUILD_JQUERY (Inhalt der Zepto Bibliothek)
  • Methode BUILD_TEXTS (Anmeldedialog in anderen Sprachen darstellen)
  • Methode BUILD_XFIELDS

Viel Spaß bei der individuellen Weiterentwicklung.