Jak to działa w trzech krokach
Logowanie kontem firmowym oznacza, że aplikacja nie prowadzi własnej listy haseł. Pracownik wpisuje ten sam login i hasło co do komputera, a aplikacja pyta o nie katalog firmy, czyli Active Directory.
- Wyszukanie użytkownika
Aplikacja łączy się z kontrolerem domeny kontem serwisowym i szuka osoby po loginie, adresie UPN albo e-mailu. Dostaje jej pełny identyfikator w katalogu i listę grup.
- Sprawdzenie hasła
Aplikacja próbuje połączyć się z kontrolerem jako ten użytkownik, z hasłem, które wpisał. Jeśli kontroler przyjmie połączenie, hasło jest poprawne. Samo hasło nigdzie się nie zapisuje.
- Nadanie uprawnień
Na podstawie grup aplikacja ustala, czy osoba w ogóle może wejść, do których modułów i z jaką rolą. Robi to przy każdym logowaniu, więc zmiana w katalogu działa od razu przy następnym wejściu.
Co musi przygotować dział IT
Uprawnienia z grup zamiast ręcznego nadawania
Najprostszy model, który dobrze się skaluje, ma trzy warstwy: grupa wejściowa (kto w ogóle się zaloguje), grupy modułów (gdzie wchodzisz) i grupy ról (jak głęboko widzisz w środku). Moduł i rola to dwa niezależne wymiary: ktoś może mieć dostęp do raportu bez prawa do notatek wewnętrznych.
| Grupa w AD | Co daje |
|---|---|
| APP-Uzytkownicy | Wejście do aplikacji. Kto nie jest w tej grupie, nie zaloguje się wcale. |
| APP-Modul-Parking | Dostęp do jednego modułu, tu: rezerwacji parkingu. |
| APP-Rola-Analityk | Rola w modułach: na przykład widok szczegółów, których zwykły użytkownik nie widzi. |
| APP-Rola-Admin | Pełne uprawnienia administracyjne. Przy kilku rolach wygrywa wyższa. |
Gdy ktoś nie ma dostępu do żadnego modułu, pokaż mu po zalogowaniu jasny komunikat, zamiast błędu logowania. Błąd logowania wysyła człowieka do resetu hasła, choć hasło jest dobre.
Pułapki, które wychodzą dopiero w praktyce
- Grupy zagnieżdżone
Atrybut memberOf zwraca tylko członkostwa bezpośrednie. Jeśli ktoś jest w grupie przez inną grupę, aplikacja tego nie zobaczy, chyba że zapyta o to osobno (w AD służy do tego specjalna reguła dopasowania). Najprościej ustalić, że grupy aplikacji zawierają ludzi bezpośrednio.
- Login, UPN czy e-mail
Raz ktoś wpisze jan.kowalski, innym razem jan.kowalski@firma.pl. Konto w aplikacji zakładaj zawsze pod jednym identyfikatorem (np. sAMAccountName), inaczej jedna osoba dorobi się dwóch kont.
- Puste hasło
Część serwerów traktuje połączenie z pustym hasłem jako anonimowe i zgłasza sukces. Aplikacja musi odrzucić pusty login albo hasło, zanim w ogóle zapyta kontroler.
- Blokada prób
Kilka błędnych prób powinno blokować logowanie w aplikacji na kilka minut. Inaczej skrypt zgadujący hasła zablokuje konto domenowe pracownika w całej firmie.
- Certyfikat kontrolera
Szyfrowanie bez sprawdzania certyfikatu chroni przed podsłuchem, ale nie przed podstawionym serwerem. Wskaż aplikacji plik z certyfikatem urzędu i włącz weryfikację.
Przykład w Pythonie
Szkic logiki z biblioteką ldap3, która jest czysto pythonowa i instaluje się bez dodatkowych programów, także na Windows. W prawdziwej aplikacji dochodzą limity czasu, dziennik logowań i blokada prób.
import os, ssl
from ldap3 import Server, Connection, Tls, SUBTREE
from ldap3.core.exceptions import LDAPException
from ldap3.utils.conv import escape_filter_chars
TLS = Tls(validate=ssl.CERT_REQUIRED, ca_certs_file="C:/certs/firma-ca.pem")
SERVER = Server("dc01.firma.local", port=636, use_ssl=True, tls=TLS)
BASE = "DC=firma,DC=local"
KONTO = "CN=svc-aplikacja,OU=Serwisowe,DC=firma,DC=local"
def zaloguj(login, haslo):
if not login or not haslo: # pusty login albo hasło: kontrolera nie pytamy wcale
return None
f = escape_filter_chars(login)
with Connection(SERVER, KONTO, os.environ["LDAP_SVC_PASSWORD"], auto_bind=True) as svc:
svc.search(BASE, f"(|(sAMAccountName={f})(userPrincipalName={f})(mail={f}))",
SUBTREE, attributes=["sAMAccountName", "displayName", "memberOf"])
if len(svc.entries) != 1:
return None
user = svc.entries[0]
try: # hasło sprawdza kontroler: próba połączenia jako ten użytkownik
Connection(SERVER, user.entry_dn, haslo, auto_bind=True).unbind()
except LDAPException:
return None
return {"login": user.sAMAccountName.value, "nazwa": user.displayName.value,
"grupy": list(user.memberOf.values)}W Django taki kod zamyka się we własnym backendzie uwierzytelniania (ustawienie AUTHENTICATION_BACKENDS). Przy każdym logowaniu backend zakłada albo aktualizuje konto Django i zapisuje grupy jako role.
Pojęcia z tego poradnika
- Active Directory
Katalog użytkowników, grup i komputerów w firmowej sieci Windows. Aplikacja wewnętrzna może z niego brać logowanie i uprawnienia.
- LDAP i LDAPS
Protokół, którym aplikacja rozmawia z katalogiem użytkowników: wyszukuje konta i sprawdza hasła. LDAPS to jego wersja szyfrowana, zwykle na porcie 636.
Pytania i odpowiedzi
Czy aplikacja przechowuje hasła pracowników?
Nie. Hasło trafia tylko do kontrolera domeny w chwili logowania. Aplikacja zapisuje wynik: kto i kiedy się zalogował.
Firma ma Microsoft 365, a nie własny serwer AD. Co wtedy?
Wtedy logowanie robi się zwykle przez Microsoft Entra ID i protokół OpenID Connect, a nie LDAP. Zasada jest ta sama: hasło sprawdza Microsoft, a aplikacja dostaje informację o użytkowniku i jego grupach.
Co się dzieje, gdy kontroler domeny nie działa?
Nikt nowy się nie zaloguje, a zalogowani pracują do końca sesji. Dlatego w konfiguracji podaje się co najmniej dwa kontrolery.
Czy da się logować bez szyfrowania?
Technicznie tak, ale wtedy hasła domenowe idą przez sieć otwartym tekstem. Microsoft od lat zaleca podpisywanie i szyfrowanie połączeń LDAP, więc to nie jest opcja do produkcji.
Źródła i dokumentacja
- ldap3: dokumentacja biblioteki LDAP dla Pythonaldap3.readthedocs.io
- Django: własny sposób uwierzytelnianiadocs.djangoproject.com
- Microsoft: włączanie podpisywania LDAP w Windows Serverlearn.microsoft.com
- Microsoft ADV190023: podpisywanie i wiązanie kanału LDAPmsrc.microsoft.com