środa, 5 września 2018

Uwierzytelnianie użytkownika poprzez MS Active Directory

Jedną z zalet korzystania z zewnętrznych dostawców tożsamości jest to, że użytkownik nie musi tworzyć i zapamiętywać hasła do kolejnej aplikacji. W tym artykule opisuję jak w szybki sposób utworzyć uwierzytelnianie z pomocą kontrolera domeny MS Active Directory.


W naszej funkcji skorzystamy z paczki dbms_ldap, dostępnej w bazie Oracle. Poniżej opis najistotniejszych fragmentów kodu funkcji.

Aby móc naszą funkcję wykorzystać w Custom Authentication powinna ona przyjmować dwa parametry typu varchar2 p_username i p_password oraz zwracać typ boolean
function my_ldap_auth( p_username in varchar2, p_password in varchar2 ) return boolean
Zakładamy, że pełna nazwa naszej domeny to my.domain.com, krótka nazwa to my, kontroler domeny stoi pod adresem 192.168.0.1, a konta użytkowników znajdują się w OU Users
    ldap_host     varchar2(256) := '192.168.0.1'; -- address of domain controller
    ldap_port     varchar2(256) := '389'; -- default port of domain controller
    ldap_base     varchar2(256) := 'OU=Users,DC=my,DC=domain,DC=com'; -- OU with Users
    l_dn_prefix   varchar2(100) := 'my\'; -- short domain name 
    l_dn_suffix   varchar2(100) := '@my.domain.com'; -- long domain name
Sprawdzamy, czy użytkownik podał hasło i odrzucamy uwierzytelnianie w przeciwnym wypadku
    if p_password is null or p_password = '' then return false; end if;
Otwieramy połączenie do naszego kontrolera domeny
    dbms_ldap.use_exception := true;
    l_session := dbms_ldap.init( hostname =>ldap_host, portnum  => ldap_port );
Uwaga! Użytkownik uwierzytelniając się w Active Directory może użyć trzech różnych z naszego punktu widzenia loginów: UserPrincipalName, UserPrincipalName@FullDomainName oraz ShortDomainName\SamAccountName, czyli np. pawel, pawel@my.domain.com oraz my\pawp. Taką wartość będzie również posiadał item APP_USER. Musimy zatem pamiętać, aby po uwierzytelnieniu znaleźć unikalny identyfikator użytkownika w aplikacji.

Ze względów organizacyjnych w naszym systemie dopuściliśmy logowanie przy pomocy SamAccountName, czyli pawp zamiast my\pawp (dodatkowo wszystkie SamAccountName były inne niż UserPrincipalName i były 4 znakowe).

Funkcja dbms_ldap.simple_bind_s zwraca wartość 0, gdy znajdzie użytkownika o podanym Distinguished Name i poprawnym haśle. Natomiast zwraca również 0 w przypadku, gdy nie podamy hasła w ogóle! Dlatego w jednym z poprzednich kroków sprawdzaliśmy, czy hasło nie jest puste.

Kod obejmujący powyższe przypadki wygląda następująco
    if length(p_username)=4 
    then
        retval := dbms_ldap.SIMPLE_BIND_S( ld     => l_session
                                   , dn     => l_dn_prefix||p_username
                                   , passwd => p_password );
    else
        if instr(p_username,'@') = 0 then
            retval := dbms_ldap.SIMPLE_BIND_S( ld     => l_session
                                   , dn     => p_username|| l_dn_suffix
                                   , passwd => p_password );
        else
            retval := dbms_ldap.SIMPLE_BIND_S( ld     => l_session
                                   , dn     => p_username
                                   , passwd => p_password );
        end if;
    end if;
Gdy uwierzytelnienie się powiodło, będziemy zwracać wartość true
    l_authed := case when retval = 0 then true else false end;
Odłączamy się od kontrolera domeny
    retval := dbms_ldap.unbind_s( ld => l_session );
Ostatecznie cała funkcja wygląda w następujący sposób: Kliknij tutaj, aby rozwinąć:

Zostaje jeszcze utworzyć Custom Authentication, gdzie jako funkcję uwierzytelniającą należy podać naszą funkcję my_ldap_auth.

Nie zapominajmy jeszcze o utworzeniu stosownych ACL, aby nasza baza Oracle mogła połączyć się z kontrolerem domeny: Kliknij tutaj, aby rozwinąć:



3 komentarze:

  1. Bardzo fajny artykuł. Drobna uwaga pusty string jest tożsamy z null zatem nie trzeba dwa razy sprawdzać hasła.

    OdpowiedzUsuń
    Odpowiedzi
    1. Dziękuję za cenną uwagę. Osobiście kilka razy przejechałem się na warunkach z nullami, dlatego wolę dmuchać na zimne. Osobny warunek dla null i osobny dla pustych stringów czy zer. Pozdrawiam.

      Usuń
  2. Autoryzacja z pomocą LDAP to jeszcze nie jest problem, gorzej jest kiedy najdzie nas ochota na autentykację na podstawie grup LDAP. Napisałem sobie dwa pluginy które to jakoś rozwiązują:
    https://github.com/gisprogrammer/Oracle-APEX-Authentication-and-Authorization-plugins

    OdpowiedzUsuń