Reise durch den DRF-Quellcode: Warum ich immer nur benutzerdefinierte Authentifikatoren erstellt habe und die Wiederentdeckung der 'integrierten Authentifikatoren'
1. Ich liebe DRF, aber ich habe den integrierten Authentifikatoren nie vertraut.
Django und DRF (Django Rest Framework) sind feste Begleiter in meinem Entwicklungsleben. Doch wenn es um Authentifizierung geht, hatte ich immer eine gewisse Sturheit. Ich bevorzuge hauptsächlich JWT-, API Key- oder OAuth2-Methoden, da diese in modernen App-Umgebungen der Standard sind.
Es war jedoch merkwürdig, dass die offizielle DRF-Dokumentation eher 'altmodische' Authentifizierungsklassen wie Basic, Session, Token, Remote in den Vordergrund stellte, anstatt der modernen Methoden, die ich nutze. Oft fragte ich mich: "Warum integriert das fähige DRF solche Methoden?" Aus diesem Grund habe ich immer meine eigenen benutzerdefinierten Authentifikatoren erstellt, indem ich BaseAuthentication geerbt habe.
2. Warum ich den Quellcode der 'integrierten Authentifikatoren' erneut öffne?
Als ich benutzerdefinierte Authentifikatoren entwickelte, kam mir plötzlich ein Gedanke:
"Ist der von mir erstellte Authentifikator wirklich perfekt in die Philosophie von Django und DRF integriert?"
Ich begann mir Sorgen zu machen, dass ich möglicherweise die 'Natürlichkeit' des von DRF entworfenen Authentifizierungsschemas übersehe, die über das bloße Hinzufügen eines Benutzers zu request.user hinausgeht. Daher werde ich ab heute in mehreren Beiträgen die vier integrierten DRF-Authentifikatoren detailliert untersuchen:
- BasicAuthentication (HTTP-Basisauthentifizierung)
- SessionAuthentication (Nutzung von Django-Sessions)
- TokenAuthentication (Einfache Token-Authentifizierung)
- RemoteUserAuthentication (Integration externer Authentifizierung)
3. Die 'Philosophie' hinter dem 'Unbehagen' entdecken
Ehrlich gesagt ist es unangenehm, BasicAuthentication in modernen Diensten direkt zu verwenden. Es wirkt anfällig für Sicherheitslücken, und die Struktur, bei der jedes Mal Benutzername und Passwort gesendet werden, fühlt sich unsicher an. Doch wenn man den Quellcode analysiert, entdeckt man ein sehr ausgeklügeltes Design dafür, wie mit dem Client bei einem Authentifizierungsfehler kommuniziert werden soll.
Zum Beispiel den Unterschied zwischen dem bloßen Senden eines 403 Forbidden und dem Induzieren eines standardkonformen 401 Unauthorized über authenticate_header. Solche Details waren der Schlüssel zur Entwicklung einer "Django-typischen" Klasse.
4. Ziel dieser Serie: Benutzerdefinierte Authentifikatoren 'nahtlos' integrieren
Am Ende dieser Serie möchte ich nicht nur reines Wissen erlangen.
- Inspiration: Wir lernen die Designphilosophie und die Struktur der integrierten Authentifikatoren kennen.
- Portierung: Diese Philosophie übertragen wir auf meine bevorzugten benutzerdefinierten JWT- oder OAuth2-Authentifikatoren.
- Harmonie: So sollen meine benutzerdefinierten Authentifikatoren im DRF-Authentifizierungssystem so nahtlos funktionieren, als wären sie von Anfang an dort gewesen.
Dies ist der erste Schritt, um ein Entwickler zu werden, der nicht nur Funktionen implementiert, sondern die Philosophie des Frameworks in den Code integrieren kann. Die DRF-Authentifikator-Quellcode-Analyse-Serie beginnt jetzt.

5. Ein Blick in den Quellcode: BasicAuthentication
Die Einleitung war lang. Nun öffnen wir das Herzstück der BasicAuthentication.
Wenn man rest_framework/authentication.py von DRF öffnet, stößt man auf die Realität dieser Klasse. Sie ist überraschend kompakt, aber enthält robuste Regeln.
class BasicAuthentication(BaseAuthentication):
"""
HTTP Basic authentication against username/password.
"""
www_authenticate_realm = 'api'
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != b'basic':
return None
if len(auth) == 1:
msg = _('Invalid basic header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid basic header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
try:
auth_decoded = base64.b64decode(auth[1]).decode('utf-8')
except UnicodeDecodeError:
auth_decoded = base64.b64decode(auth[1]).decode('latin-1')
userid, password = auth_decoded.split(':', 1)
except (TypeError, ValueError, UnicodeDecodeError, binascii.Error):
msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(userid, password, request)
def authenticate_credentials(self, userid, password, request=None):
credentials = {
get_user_model().USERNAME_FIELD: userid,
'password': password
}
user = authenticate(request=request, **credentials)
if user is None:
raise exceptions.AuthenticationFailed(_('Invalid username/password.'))
if not user.is_active:
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
return (user, None)
def authenticate_header(self, request):
return 'Basic realm="%s"' % self.www_authenticate_realm
Warum gerade der Header?
Normalerweise denken wir zuerst an request.POST oder request.data, wenn wir Daten senden. Doch Authentifizierungsinformationen im Header zu übermitteln, ist die gängige Regel. Warum?
Authentifizierung muss flexibel auf alle HTTP-Methoden (GET, POST, PUT, DELETE usw.) reagieren können. GET-Anfragen haben überhaupt keinen Body, und DELETE hat oft ebenfalls einen leeren Body. Wenn Authentifizierungsinformationen im Body enthalten sein müssten, müssten wir ein skurriles Design implementieren, bei dem wir sogar für Abfragen gezwungen wären, POST zu verwenden. Die Authentifizierung im Header ist wie die Vereinbarung: "Egal welche Tür du öffnest, halte deinen Ausweis in der Hand."
Die nüchterne Realität: Sicherheitslücken
Der größte Nachteil dieser Methode ist Base64. Es ist eine reine 'Kodierung', keine Verschlüsselung. Jeder kann in einer Sekunde mit einer Dekodierungsseite Ihren Benutzernamen und Ihr Passwort herausfinden.
Fazit: Im HTTP-Umfeld ist die Verwendung absolut verboten. Nur innerhalb eines sicheren Tunnels wie HTTPS und auch dort nur für sehr begrenzte Zwecke sollte sie eingesetzt werden.
Erste Inspiration: Kosteneffiziente Sicherheit für Machine-to-Machine-Kommunikation (M2M)
Dennoch gibt es Momente, in denen dieser alte Authentifikator reizvoll ist. Nämlich bei der Kommunikation zwischen Servern. Wenn es zu aufwendig ist, ein HMAC oder ein komplexes API Key-System aufzubauen, aber das Senden von Daten im Klartext unsicher wäre, können wir diese Struktur 'leicht' abändern.
- Benutzerdefinierte Verschlüsselung: Statt Base64 senden wir binäre Inhalte, die mit einem symmetrischen Schlüssel verschlüsselt sind, den nur beide Server kennen.
- Einfache Implementierung: Auch ohne komplexen Handshake ist eine schnelle und sichere Kommunikation in Form von
Basic <Encrypted_Data>möglich. Es wird zu einem effizienten Kanal, über den wir "unsere geheimen Passwörter" austauschen.
Zweite Inspiration: Das verlorene Zahnrad authenticate_header()
Die bemerkenswerteste Methode ist authenticate_header(). Nirgendwo im Quellcode wird sie direkt aufgerufen. Doch diese Methode ist der entscheidende Schlüssel, der im DRF-Ausnahmebehandler den '401 Unauthorized' erzeugt.
Beim Erstellen benutzerdefinierter Authentifikatoren übersehen wir häufig das Weglassen dieser Methode. Tatsächlich hat das Weglassen dieser Methode keinen Einfluss auf den Authentifizierungsprozess selbst. Es lohnt sich jedoch zu vergleichen, wie dieser kleine Unterschied den Code auf der View-Ebene unordentlich macht.
Fall 1. Wenn authenticate_header weggelassen wird (manuelle Methode)
Wenn diese Methode fehlt, geht DRF davon aus, dass es "keine Anleitung zur Authentifizierung geben kann" und gibt bei einem Authentifizierungsfehler einen 403 Forbidden anstelle eines 401 zurück. Um dies zu vermeiden, ist in der View folgender 'mühsamer' Code erforderlich:
# ❌ Wenn authenticate_header fehlt
class MyPostAPIView(APIView):
# Wenn IsAuthenticated verwendet wird, wird immer 403 zurückgegeben, daher erlauben wir zunächst alles für 401.
permission_classes = [AllowAny]
def post(self, request):
# Innerhalb der View muss die Authentifizierung manuell überprüft und 401 zurückgegeben werden.
if not request.user or not request.user.is_authenticated:
return Response(
{"detail": "Login erforderlich."},
status=status.HTTP_401_UNAUTHORIZED
)
# ... eigentliche Logik beginnt ...
Fall 2. Wenn authenticate_header implementiert wird (Implementierung der DRF-Philosophie)
Wenn hingegen in einem benutzerdefinierten Authentifikator nur eine einzige Anweisung (authenticate_header) hinterlegt wird, erkennt das große DRF-Authentifizierungsschema dies und leitet die 401-Antwort von selbst ab.
# ✅ Wenn authenticate_header vorhanden ist
class MyPostAPIView(APIView):
# DRF gibt nun automatisch 401 zurück, daher können Berechtigungen deklarativ gesteuert werden.
permission_classes = [IsAuthenticated]
def post(self, request):
# Die View konzentriert sich ausschließlich auf die 'Geschäftslogik'.
# Die Behandlung von Authentifizierungsfehlern ist bereits auf der oberen Ebene (in der authenticate_header-Methode des Authentifikators) abgeschlossen.
...
Dies ist die wahre "Django-Ästhetik". Anstatt fragmentierten Code in der View zu verstreuen, fühlt es sich an, als würde man seinen Code perfekt in die Zahnräder des Frameworks einfügen. Mit dieser einen Methode können wir die Rollen von 'Authentifizierung' und 'Autorisierung' klar trennen und die View deutlich schlanker halten.
Der wahre Charme von DRF: Die stille Nachgiebigkeit (Die Ästhetik von None)
Werfen wir zum Schluss noch einmal einen Blick auf diesen Teil des Quellcodes:
if not auth or auth[0].lower() != b'basic':
return None
Wenn das Format nicht stimmt, wird kein Fehler ausgegeben, sondern None zurückgegeben und sich still zurückgezogen. Diese kleine Behandlung schafft die Flexibilität von DRF. Dank dessen können wir mehrere Authentifikatoren nacheinander in die AUTHENTICATION_CLASSES-Liste aufnehmen. Spürt man nicht die Spuren der Entwickler, die sich gegenseitig Rücksicht nehmen: "Ah, das ist nicht meine Methode? Dann schau dir den nächsten Authentifikator an!"?
Fazit
BasicAuthentication mag veraltet erscheinen, doch seine Struktur bildet die Grundlage moderner Authentifizierungssysteme. Insbesondere die Behandlung von Ausnahmen und die Unterstützung mehrerer Authentifizierungsmechanismen sind wertvolle Erkenntnisse, die wir beim Erstellen benutzerdefinierter Authentifikatoren unbedingt übernehmen sollten.
Nun haben wir den ersten Berg überquert. Im nächsten Beitrag werden wir uns mit der uns vertrauten, aber immer noch rätselhaften SessionAuthentication befassen.
"Fakten, die Sie über die Session-Authentifizierung nicht wussten, obwohl Sie sie verwendet haben", seien Sie gespannt!
Es sind keine Kommentare vorhanden.