prog:oracle_apex_active_directory_integration
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
Beide Seiten der vorigen RevisionVorhergehende Überarbeitung | |||
prog:oracle_apex_active_directory_integration [2016/05/19 20:00] – [PWD des Domain Users verstecken] gpipperr | prog:oracle_apex_active_directory_integration [2018/04/24 00:26] (aktuell) – gpipperr | ||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
+ | ===== Oracle Apex 5 Securtiy und Microsoft Active Directory Integration ===== | ||
+ | **Erstellt 05/2016** | ||
+ | |||
+ | Um Zugriffe auf eine Apex Applikation zu steuern, kann auch das Active Directory eingebunden werden. | ||
+ | |||
+ | Zwei Aufgaben sind dabei zu lösen, den User zu authentisieren und den Zugriff auf die Pages der Applikation zu autorisieren. | ||
+ | |||
+ | Im folgenden Beispiel erfolgt die Authentifizierung über den Apache Webserver mit NTLM und für die Autorisierung von Teilen der Applikation wird auf die Hinterlegten Gruppen im AD zurückgegriffen. | ||
+ | |||
+ | Die AD Gruppen werden dynamisch in den Session Kontext des aktuellen Users übernommen und werden dann in der Apex Applikation über einen " | ||
+ | |||
+ | |||
+ | Übersicht: | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | ---- | ||
+ | |||
+ | ==== Authentisieren über den Webserver ==== | ||
+ | |||
+ | Ob ein User sich in der Domaine angemeldet hat muss der eingesetzte Webserver prüfen. | ||
+ | |||
+ | ===Single Sign On unter Windows 2012 mit Apache HTTP und dem NTLM Module === | ||
+ | |||
+ | Unter Windows kann am einfachsten mit dem Apache HTTP Server NTLM mit **Mod Auth NTLM ** eingesetzt werden. | ||
+ | Siehe dazu => [[prog: | ||
+ | |||
+ | Über das Modul wird geprüft, ob der User sich gültig in der Windows Domain angemeldet hat, ist das True wird auf die Apex Application weitergleitet. | ||
+ | |||
+ | Die weitere Prüfung, ob der User fachlich die Anwendung bzw. einzelnen Seiten oder Menüpunkte sehen darf, muss in der Apex Applikation getestet werden. | ||
+ | |||
+ | Der Domain Name des User wird in den HTTP Header geschrieben (siehe die NTLM Konfiguration des obigen Beispiels) und in eine von uns definiert Variable abgelegt, im diesem Beispiel in " | ||
+ | |||
+ | |||
+ | ---- | ||
+ | |||
+ | |||
+ | ==== Apex - Authentication Scheme einrichten - Wie meldet man sich in Apex an?==== | ||
+ | |||
+ | Die Windows Anmeldung bzw. in Prinzip nur der Windows User Name wird an die Single Sign On Konfiguration von Apex für die jeweilige Applikation weitergereicht. | ||
+ | |||
+ | Damit sich nun der User nicht nicht nochmal anmelden muss, muss in APEX das " | ||
+ | |||
+ | * In der Applikation auf " | ||
+ | * Create Button rechte wählen | ||
+ | * Vorlage auswählen : Based on a pre-configured scheme from the gallery => Next{{ : | ||
+ | * Nun einen Namen vergeben und die Methode **HTTP HEADER Variable** auswählen {{ : | ||
+ | * Hinterlegen in welchen Header Feld der Username steht {{ : | ||
+ | * Speichern | ||
+ | |||
+ | |||
+ | In den Application Settings prüfen, ob auch das richtige Schema gewählt wurde: | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | |||
+ | Nun kann der Test beginnen ob das auch geklappt hat. | ||
+ | |||
+ | |||
+ | Außerhalb der Domain sollte jetzt eine Benutzerabfrage erfolgen, angemeldet an der Domain sollte sich die Applikation ohne Login starten lassen. | ||
+ | |||
+ | ==Debuggen== | ||
+ | |||
+ | Klappt es nicht, die Header Variablen auf einer Seite mit Hilfe von " | ||
+ | <code plsql> | ||
+ | begin | ||
+ | | ||
+ | | ||
+ | | ||
+ | end; | ||
+ | </ | ||
+ | |||
+ | Den Variablen Namen haben wir zuvor in der Apache Konfiguration auf NTLM Ebene festgelegt! | ||
+ | |||
+ | |||
+ | ---- | ||
+ | |||
+ | ---- | ||
+ | |||
+ | ==== Autorisieren - In Apex prüfen ob der jeweilige User die Seite auch wirklich sehen darf ==== | ||
+ | |||
+ | |||
+ | In der Windows Welt ist das Gruppen Konzept selbstverständlich um Rechte auf Objekte im Betriebssystem zu definieren. | ||
+ | |||
+ | Das gleiche kann auch in Apex erfolgen, über Gruppen kann der Zugriff auf Seiten, einzelne Elemente der Seite und Menüeinträge gesteuert werden. | ||
+ | |||
+ | |||
+ | In Apex 5 kann die Zugehörigkeit zu einer Gruppe für den aktuelle angemeldeten Anwender beim Login gesetzt werden. | ||
+ | |||
+ | |||
+ | Um das Verhalten einfach zu testen ein statisches Beispiel als Procedure zwei Gruppen zum Testen (angelegt im Parsing Schema der Apex Application) erstellen: | ||
+ | <code plsql> | ||
+ | create procedure setRole | ||
+ | is | ||
+ | begin | ||
+ | apex_authorization.enable_dynamic_groups ( p_group_names => apex_t_varchar2(' | ||
+ | end; | ||
+ | / | ||
+ | </ | ||
+ | => https:// | ||
+ | |||
+ | Nun wird diese Procedure im **Authentication Scheme** als die " | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | |||
+ | |||
+ | Der Trick dahinter ist es nun über das Active Directory die Gruppen des Windows Users auszulesen und diese Windows Gruppen dynamisch in den Session Kontext von Apex zu schrieben. | ||
+ | |||
+ | === Apex - Authorization Schemes verwenden === | ||
+ | |||
+ | In Apex wird ein " | ||
+ | |||
+ | In der Apex Application wird dann auf Seiten oder Menü Ebene dieses " | ||
+ | |||
+ | |||
+ | * In der Applikation auf „Shared Components“ klicken, „Authorization Schemes“ auswählen | ||
+ | * Mit den " | ||
+ | * "From Scratch" | ||
+ | * Name vergeben | ||
+ | * Scheme Type "Is in Group" wählen | ||
+ | * Eine Gruppe einfach eintragen (nicht über die Dialog suchen, | ||
+ | * {{ : | ||
+ | * Mit " | ||
+ | |||
+ | |||
+ | |||
+ | In der Applikation nun auf der jeweiligen Seite das Authorization Scheme in den Security Settings hinterlegen: | ||
+ | * {{ : | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ---- | ||
+ | |||
+ | ==== Mit DBMS_LDAP das AD abfragen ==== | ||
+ | |||
+ | Nach dem nun in den obigen Schritte mit der statischen Methode alles geklappt hat, gilt es nun das ganze dynamisch auch aus dem AD zu füllen. | ||
+ | |||
+ | |||
+ | |||
+ | Im ersten Schritt muss geprüft werden ob aus der Datenbank überhaupt auf das AD zugegriffen werden kann. | ||
+ | |||
+ | Ein sehr gutes Beispiel um das zu testen findet sich hier => https:// | ||
+ | |||
+ | Den Beispiel Code aus diesem guten Beispiel in eine Datei kopieren und UNTER dem Parsing Schema der Apex Applikation den Zugriff auf das AD prüfen. | ||
+ | |||
+ | Meist muss unter 11g und 12c eine Netzwerk ACL hinterlegt werden, um das überhaupt verwenden zu können, siehe dazu mehr am Ende dieser Seite. | ||
+ | |||
+ | Mit diesen Schritt werden dann die notwendigen Zugangsdaten (AD IP Adresse und Port) + Username und Passwort ermittelt und die richtige LDAP Abfrage Struktur wird geklärt. | ||
+ | |||
+ | |||
+ | Mit diese Daten kann dann mein Code Beispiel ldap_ad_util ergänzt werden. | ||
+ | |||
+ | === LDAP_AD_UTIL === | ||
+ | |||
+ | Mit Hilfe des Windows User Name wird das AD abgefragt, die gefunden Gruppe werden in den Session Kontext von Apex geschrieben. | ||
+ | |||
+ | Das Schreiben in den Session Kontext von APEX erfolgt durch das Hinterlegen einer Procedure in der current " | ||
+ | |||
+ | == Der Code == | ||
+ | |||
+ | Für die dynamische Übernahme der Gruppen aus dem AD nach Apex wird der folgende PL/SQL verwendet (muss im Pasing Schema liegen oder von dort mit entsprechenden Rechten lesbar sein). | ||
+ | |||
+ | Spezifikation: | ||
+ | |||
+ | <code plsql ldap_ad_util_spc.sql> | ||
+ | create or replace package ldap_ad_util | ||
+ | is | ||
+ | -- +============================================================================ | ||
+ | -- | ||
+ | -- | ||
+ | -- | ||
+ | -- +============================================================================ | ||
+ | | ||
+ | | ||
+ | -- exception handling | ||
+ | g_pck | ||
+ | | ||
+ | ex_gperrors exception; | ||
+ | pragma exception_init (ex_gperrors, | ||
+ | g_emerrors varchar2 (100) := 'An error occured. Please view the ERRORS-table for more information.'; | ||
+ | |||
+ | -- global variables | ||
+ | -- You have to edit carefully this section to set all the values of your enviroment! | ||
+ | |||
+ | -- IP or name of the AD and the port | ||
+ | g_ldap_host | ||
+ | g_ldap_port | ||
+ | |||
+ | -- user to read from the AD | ||
+ | g_ldap_user | ||
+ | g_ldap_passwd | ||
+ | | ||
+ | -- The entry to the AD tree | ||
+ | -- check the cn ! and adjust to your needs! | ||
+ | g_ldap_base | ||
+ | |||
+ | -- how to seach the user in the ad | ||
+ | -- check how the loginname in your domain is defined! | ||
+ | g_ad_user_type varchar2(255) := ' | ||
+ | | ||
+ | | ||
+ | -- +===========================================================+ | ||
+ | -- function : connectAD | ||
+ | -- +===========================================================+ | ||
+ | function connectad | ||
+ | return dbms_ldap.session; | ||
+ | -- +===========================================================+ | ||
+ | -- procedure : disconnectAD | ||
+ | -- +===========================================================+ | ||
+ | procedure disconnectad( | ||
+ | p_session in out dbms_ldap.session); | ||
+ | -- +===========================================================+ | ||
+ | -- procedure : disconnectAD | ||
+ | -- +===========================================================+ | ||
+ | function readgroups( | ||
+ | p_session dbms_ldap.session , | ||
+ | p_username varchar2) | ||
+ | return apex_t_varchar2; | ||
+ | -- +===========================================================+ | ||
+ | -- procedure : setApexGroups | ||
+ | -- set in the Apex Session dynamic groups | ||
+ | -- +===========================================================+ | ||
+ | procedure setapexgroups( | ||
+ | p_username varchar2 default sys_context( | ||
+ | ' | ||
+ | ' | ||
+ | end ldap_ad_util; | ||
+ | / | ||
+ | |||
+ | </ | ||
+ | |||
+ | Body: | ||
+ | |||
+ | <code plsql ldap_ad_util_spc.sql> | ||
+ | create or replace package body ldap_ad_util | ||
+ | is | ||
+ | -- +============================================================================ | ||
+ | -- | ||
+ | -- | ||
+ | -- GPI 2016 | ||
+ | -- +============================================================================ | ||
+ | -- +===========================================================+ | ||
+ | -- function : getADPWD | ||
+ | -- get the Password for the AD User | ||
+ | -- later we will save the pwd encrypted in the database | ||
+ | -- +===========================================================+ | ||
+ | function getadpwd | ||
+ | return varchar2 | ||
+ | is | ||
+ | begin | ||
+ | return g_ldap_passwd; | ||
+ | end; | ||
+ | -- +===========================================================+ | ||
+ | -- function : connectAD | ||
+ | -- connect to the AD | ||
+ | -- +===========================================================+ | ||
+ | function connectad | ||
+ | return dbms_ldap.session | ||
+ | is | ||
+ | v_session dbms_ldap.session; | ||
+ | v_retval pls_integer; | ||
+ | begin | ||
+ | -- choose to raise exceptions. | ||
+ | dbms_ldap.use_exception | ||
+ | dbms_ldap.utf8_conversion: | ||
+ | -- connect to the ldap server. | ||
+ | v_session := dbms_ldap.init(hostname => g_ldap_host, | ||
+ | -- connect with this user to the AD | ||
+ | v_retval := dbms_ldap.simple_bind_s(ld => v_session , dn => g_ldap_user , passwd => getadpwd ); | ||
+ | if v_retval != DBMS_LDAP.SUCCESS then | ||
+ | | ||
+ | | ||
+ | end if; | ||
+ | return v_session; | ||
+ | exception | ||
+ | when others then | ||
+ | dbms_output.put_line(' | ||
+ | raise_application_error( sqlcode , '-- Error at::' | ||
+ | end connectad; | ||
+ | -- +===========================================================+ | ||
+ | -- procedure : disconnectAD | ||
+ | -- +===========================================================+ | ||
+ | procedure disconnectad( | ||
+ | p_session in out dbms_ldap.session) | ||
+ | is | ||
+ | v_retval pls_integer; | ||
+ | begin | ||
+ | v_retval := dbms_ldap.unbind_s(ld => p_session); | ||
+ | if v_retval != DBMS_LDAP.SUCCESS then | ||
+ | | ||
+ | | ||
+ | end if; | ||
+ | exception | ||
+ | when others then | ||
+ | dbms_output.put_line(' | ||
+ | end disconnectad; | ||
+ | | ||
+ | -- +===========================================================+ | ||
+ | -- procedure : disconnectAD | ||
+ | -- Code Logic copied from https:// | ||
+ | -- Thanks to Tim Hall | ||
+ | -- +===========================================================+ | ||
+ | function readgroups( | ||
+ | p_session dbms_ldap.session , | ||
+ | p_username varchar2) | ||
+ | return apex_t_varchar2 | ||
+ | is | ||
+ | v_retval pls_integer; | ||
+ | v_attrs dbms_ldap.string_collection; | ||
+ | v_message dbms_ldap.message; | ||
+ | v_entry dbms_ldap.message; | ||
+ | v_attr_name varchar2(256); | ||
+ | v_ber_element dbms_ldap.ber_element; | ||
+ | v_vals dbms_ldap.string_collection; | ||
+ | -- empty collection | ||
+ | v_group_tab apex_t_varchar2: | ||
+ | | ||
+ | v_apex_ary apex_application_global.vc_arr2; | ||
+ | begin | ||
+ | v_attrs(1) := ' | ||
+ | -- retrieve all attributes | ||
+ | v_retval := dbms_ldap.search_s(ld => p_session | ||
+ | , base => g_ldap_base | ||
+ | , scope => dbms_ldap.scope_subtree | ||
+ | , filter => g_ad_user_type||p_username | ||
+ | , attrs => v_attrs | ||
+ | , attronly => 0 | ||
+ | , res => v_message | ||
+ | ); | ||
+ | if v_retval != DBMS_LDAP.SUCCESS then | ||
+ | | ||
+ | | ||
+ | end if; | ||
+ | |||
+ | if dbms_ldap.count_entries(ld => p_session | ||
+ | , msg => v_message) > 0 then | ||
+ | | ||
+ | -- Get all the entries returned by our search. | ||
+ | v_entry := dbms_ldap.first_entry( ld => p_session ,msg => v_message); | ||
+ | << entry_loop >> | ||
+ | while v_entry is not null | ||
+ | loop | ||
+ | -- Get all the attributes for this entry. | ||
+ | dbms_output.put_line(' | ||
+ | v_attr_name := dbms_ldap.first_attribute(ld => p_session, ldapentry => v_entry, ber_elem => v_ber_element); | ||
+ | << attributes_loop >> | ||
+ | while v_attr_name is not null | ||
+ | loop | ||
+ | -- Get all the values for this attribute. | ||
+ | v_vals := dbms_ldap.get_values (ld => p_session | ||
+ | , ldapentry => v_entry | ||
+ | , attr => v_attr_name); | ||
+ | begin | ||
+ | << values_loop >> | ||
+ | for i in v_vals.first .. v_vals.last | ||
+ | loop | ||
+ | dbms_output.put_line(' | ||
+ | | ||
+ | -- decode memberOf = CN=ORA_ASMDBA, | ||
+ | -- to the the group name | ||
+ | v_apex_ary: | ||
+ | for y in v_apex_ary.first .. v_apex_ary.last | ||
+ | loop | ||
+ | if v_apex_ary.exists(y) then | ||
+ | dbms_output.put_line(' | ||
+ | if y=1 then | ||
+ | v_group_tab.extend; | ||
+ | v_group_tab( v_group_tab.last ) := (replace(v_apex_ary(y),' | ||
+ | dbms_output.put_line(' | ||
+ | end if; | ||
+ | end if; | ||
+ | end loop; | ||
+ | end loop values_loop; | ||
+ | exception | ||
+ | when others then | ||
+ | dbms_output.put_line(' | ||
+ | end; | ||
+ | v_attr_name := dbms_ldap.next_attribute(ld => p_session | ||
+ | , ldapentry => v_entry | ||
+ | , ber_elem => v_ber_element); | ||
+ | end loop attibutes_loop; | ||
+ | v_entry := dbms_ldap.next_entry(ld => p_session | ||
+ | , msg => v_entry); | ||
+ | end loop entry_loop; | ||
+ | end if; | ||
+ | return v_group_tab; | ||
+ | end readgroups; | ||
+ | |||
+ | -- +===========================================================+ | ||
+ | -- procedure : setApexGroups | ||
+ | -- set in the Apex Session dynamic groups | ||
+ | -- +===========================================================+ | ||
+ | procedure setapexgroups( | ||
+ | p_username varchar2 default sys_context( | ||
+ | ' | ||
+ | ' | ||
+ | is | ||
+ | v_session dbms_ldap.session; | ||
+ | v_group_tab apex_t_varchar2; | ||
+ | begin | ||
+ | -- connect to LDAP | ||
+ | v_session: | ||
+ | -- read the groups into | ||
+ | dbms_output.put_line(' | ||
+ | v_group_tab: | ||
+ | -- disconnect the LDAP Session | ||
+ | disconnectad(p_session => v_session); | ||
+ | -- add this groups to the Apex Session | ||
+ | if v_group_tab.count > 0 then | ||
+ | for i in v_group_tab.first .. v_group_tab.last | ||
+ | loop | ||
+ | if v_group_tab.exists(i) then | ||
+ | dbms_output.put_line(' | ||
+ | end if; | ||
+ | end loop; | ||
+ | else | ||
+ | dbms_output.put_line(' | ||
+ | end if; | ||
+ | | ||
+ | -- set the groups with the group collection | ||
+ | -- apex_t_varchar2(' | ||
+ | apex_authorization.enable_dynamic_groups ( p_group_names => v_group_tab); | ||
+ | -- | ||
+ | | ||
+ | when others then | ||
+ | dbms_output.put_line(' | ||
+ | -- check that the connection to the ldap is closed! | ||
+ | -- Check if connection ist still open is in the function! | ||
+ | disconnectad(p_session => v_session); | ||
+ | end setapexgroups; | ||
+ | begin | ||
+ | -- Initialization | ||
+ | null; | ||
+ | end ldap_ad_util ; | ||
+ | / | ||
+ | |||
+ | </ | ||
+ | |||
+ | |||
+ | Ein noch zu lösendes Problem ist das Passwort im Package, in einer produktiven Umgebung sollte das entweder verschlüsselt hinterlegt werden oder anderweitig geschützt gespeichert sein! | ||
+ | |||
+ | Um das ganze zu testen, das Package mit den richtigen Globalen Einstellungen einspielen und in SQL*Plus im APEX | ||
+ | Parsing Schema mit einen gekannten AD User aufrufen: | ||
+ | <code sql> | ||
+ | |||
+ | set serveroutput on | ||
+ | |||
+ | exec setApexGroups(' | ||
+ | |||
+ | </ | ||
+ | |||
+ | |||
+ | Da der Default die Apex Session Info ist, braucht später keine User übergeben zu werden! | ||
+ | |||
+ | === PWD des Domain Users verstecken === | ||
+ | |||
+ | Wollte jetzt keine eigenen Tabelle für das eine Password anlegen, | ||
+ | |||
+ | Idee: | ||
+ | * Key setzt sich aus der DB Laufzeit Umgebung zusammen | ||
+ | * Key wird ergänzt um einen privaten Key bei Aufruf der Entschlüsselung. | ||
+ | * Funktion oder Object wird in der DB für das Speichern des verschlüsselten Wertes verwendet | ||
+ | |||
+ | |||
+ | Lösung: | ||
+ | |||
+ | siehe => [[dba: | ||
+ | |||
+ | |||
+ | ==== Fehler Suche - ORA-24247: network access denied by access control list (ACL) ==== | ||
+ | |||
+ | |||
+ | **Oracle 12c** Netzwerk ACL setzen! | ||
+ | |||
+ | <fc # | ||
+ | <code sql> | ||
+ | ERROR at line 1: | ||
+ | ORA-24247: network access denied by access control list (ACL) | ||
+ | ORA-06512: at " | ||
+ | ORA-06512: at " | ||
+ | ORA-06512: at " | ||
+ | ORA-06512: at " | ||
+ | ORA-06512: at line 1 | ||
+ | </ | ||
+ | |||
+ | <code sql> | ||
+ | BEGIN | ||
+ | | ||
+ | ( | ||
+ | | ||
+ | , lower_port => 389 | ||
+ | , upper_port => 389 | ||
+ | , | ||
+ | , principal_name => ' | ||
+ | , principal_type => xs_acl.ptype_db) | ||
+ | ); | ||
+ | END; | ||
+ | / | ||
+ | </ | ||
+ | |||
+ | Siehe dazu => http:// | ||
+ | |||
+ | |||
+ | ACL in 11g hinterlegen: | ||
+ | * https:// | ||
+ | |||
+ | |||
+ | ==== Quellen ==== | ||
+ | |||
+ | LDAP | ||
+ | * http:// | ||
+ | * http:// | ||
+ | * https:// | ||
+ | |||
+ | |||
+ | Verschlüsseln: | ||
+ | * http:// |
prog/oracle_apex_active_directory_integration.txt · Zuletzt geändert: 2018/04/24 00:26 von gpipperr