עיצוב מערכת אימות מודולרית עם דפוס המפעל בדנגו

יישומים מודרניים דורשים מערכות אימות כי הן גמישות ורחבות.משתמשים מצפים להיכנס באמצעות דואר אלקטרוני וסיסמה, חשבונות חברתיים, פרוטוקולים חד-on (SSO) או שיטות מבוססות-סימון, לעתים קרובות כל בתוך אותו יישום. בעוד מערכת אימות בנוי של דנגו תומך במספר רב של חזרות באמצעות ה-FLT:0 הגדרת תצורה זו היא סטטית ודורשת שרת כדי לשנות את ה-reativeerreativeerreativeerreatives, כמו גם דרישות הפעלה מחדש של משתמשים.

כדי להתגבר על המגבלות הללו, מפתחים רבים פונים לדפוסי עיצוב כמו שיטת המפעל.תבנית הבריאה הזו מספקת דרך נקייה ומוכוונת להתנגדת ללכידת רגע של אימות אחוריים, המאפשרת למערכת להסתגל בזמן ריצה מבלי להפיכה קוד הלקוח לשיעורים קונקרטיים. במאמר זה, תלמדו כיצד ליישם מערכת אימות מודולרית בדג'נגו באמצעות דפוס המפעל, עם דוגמאות אמיתיות ופרקטיקות הטובות ביותר בעולם.

אנו נבחן את מושגי הליבה מאחורי שיטת המפעל, לבנות כיתות אימות קונקרטיות עבור מספר אסטרטגיות נפוצות (שם משתמש / קצה, OAuth2, JWT, ו- Social Entry), ולבנות מפעל שבוחר את ה backend המתאים בהתבסס על קלט או תצורה.עד הסוף, יהיה לך ארכיטקטורה שניתן יהיה להוסיף שיטות אימות חדשות תוך שמירה על שאר הקוד שלך יציב.

הבנת שיטת המפעל

תבנית שיטת המפעל מגדירה ממשק ליצירת אובייקט, אך מאפשרת ל- subclasss להחליט איזה שיעור ל-Mindiate. זה שייך לקטגוריה של תבניות עיצוב הבריאה והוא שימושי במיוחד כאשר בכיתה אינה יכולה לצפות את סוג האובייקטים שהיא צריכה ליצור או כאשר היא רוצה את תת-השכבות שלה כדי לציין את האובייקטים שהיא יוצרת.

בהקשר של אימות, התבנית מאפשרת לך להגדיר ממשק משותף לכל שיטות אימות (למשל, שיטה ל- 1) ולאחר מכן ליצור יישום קונקרטי עבור כל שיטה נתמכת.במקום קידוד קשיח אשר חזרה לשימוש, אתה נציג את ההחלטה לכיתת מפעל אשר מחזירה את מקרה החזרה הנכון על בסיס פרמטרים בזמן ריצה.

שיטת המפעל נבדלת ממפעל פשוט (שיטה סטטית שבוחרת מעמד) בכך שהיא בדרך כלל מסתמכת על תת-מעמד כדי לשנות את האובייקט שנוצר.עם זאת, ב- Python ו- Django, שיטת מפעל סטטית המחזירה תת-מעמד המתאים היא לעתים קרובות מספיק ומנקה, כפי שאתה תראה למטה.אם אתה קורא לזה שיטת מפעל או מפעל סטטי, היתרונות של decoupuping לקוח קוד ישר מן ה-זמנית נשארים.

הסבר עמוק יותר לתבנית, מתייחס ל-FLT:0-מספק את שיטת המפעל של גורו

יישום התבנית ב Django

נתחיל בבניית מודול אימות מינימלי אך יקרא בייצור.מבנה הפרויקט עשוי להיראות כך:

myproject/
 authfactory/
 __init__.py
 base_auth.py
 backends.py
 factory.py
 views.py
 templates/
 settings.py

המונחים: Authentication Class

יצירת מחלקה בסיסית מופשטת המגדירה את הממשק כל חזרה קונקרטית חייבת ליישם.ממשק זה יכלול לפחות שיטת עיבוד של ההרחבה (FLT:4 או שיטות לעיבוד שלאחר האותנטיות.

# authfactory/base_auth.py
from abc import ABC, abstractmethod

class BaseAuthMethod(ABC):
 """Common interface for all authentication strategies."""

 @abstractmethod
 def authenticate(self, request):
 """
 Authenticate the user from the given request.
 Must return a User instance on success, or None on failure.
 """
 pass

 def get_user(self, user_id):
 """
 Optional method to retrieve a user object by ID.
 Can be used by backends that support session restoration.
 """
 return None

השימוש ב-FLT:6 מבטיח כי כל תת-קבוצה חייבת ליישם את ה-FLT 7 או Python יעלה את ה-FLT:8 בזמן הירגעות.

תיקון מחדש

עכשיו ליישם כיתות קונקרטיות עבור שיטות אימות הנפוצות ביותר.We'll לכלול:

  • שם המשתמש/ת-האימות (באמצעות Django's Built-in')
  • OAuth2 אימות (דוגמה לחיקוי)
  • JSON Web Token (JWT) אימות עבור לקוחות API
  • כניסה חברתית באמצעות django-allauth

שם משתמש/Password Backend

זה מחזיר את הצירים למערכת האימות של דאנגגו, אשר מעידה על קרבות וכוללת סיסמה, מתעתקת, ותכונות אבטחה אחרות.

# authfactory/backends.py
from django.contrib.auth import authenticate
from .base_auth import BaseAuthMethod

class UsernamePasswordAuth(BaseAuthMethod):
 def authenticate(self, request):
 username = request.POST.get('username')
 password = request.POST.get('password')
 return authenticate(request, username=username, password=password)

 def get_user(self, user_id):
 from django.contrib.auth import get_user_model
 User = get_user_model()
 try:
 return User.objects.get(pk=user_id)
 except User.DoesNotExist:
 return None

OAuth2 Backend

זרימת OAuth2 מורכבת יותר.דוגמה זו מראה כיצד אתה יכול לאמת אסימונים גישה המתקבלת מספק צד שלישי.

# authfactory/backends.py (continued)
import requests
from django.contrib.auth import get_user_model
from .base_auth import BaseAuthMethod

class OAuth2Auth(BaseAuthMethod):
 def __init__(self, provider_token_url, userinfo_url, client_id):
 self.provider_token_url = provider_token_url
 self.userinfo_url = userinfo_url
 self.client_id = client_id

 def authenticate(self, request):
 access_token = request.POST.get('access_token') or \
 request.META.get('HTTP_AUTHORIZATION', '').replace('Bearer ', '')
 if not access_token:
 return None
 # Verify token with the provider's introspection endpoint (simplified)
 response = requests.get(
 self.userinfo_url,
 headers={'Authorization': f'Bearer {access_token}'}
 )
 if response.status_code != 200:
 return None
 user_info = response.json()
 email = user_info.get('email')
 if not email:
 return None
 User = get_user_model()
 user, _ = User.objects.get_or_create(
 email=email,
 defaults={'username': email.split('@')[0]}
 )
 return user

 def get_user(self, user_id):
 User = get_user_model()
 try:
 return User.objects.get(pk=user_id)
 except User.DoesNotExist:
 return None

הערה: ייצור אווות 2 צריך גם לאמת את החתימה הסימון, לבדוק את ה-FLT 12, לטעון כי יישום חזק יהיה להשתמש בספריה כמו FLT:13 או FLT:14.

JWT Backend (עבור REST API)

בעת בניית API עם Django REST Framework (DRF), לעתים קרובות עליך לאמת משתמשים באמצעות JSON Web Tokens.החזרה הבאה מאמתה את JWT ולאחזר את המשתמש מהעומס.

# authfactory/backends.py (continued)
import jwt
from django.conf import settings
from django.contrib.auth import get_user_model
from .base_auth import BaseAuthMethod

class JWTAuth(BaseAuthMethod):
 def __init__(self, secret_key=None, algorithm='HS256'):
 self.secret_key = secret_key or settings.SECRET_KEY
 self.algorithm = algorithm

 def authenticate(self, request):
 token = request.META.get('HTTP_AUTHORIZATION', '').replace('Bearer ', '')
 if not token:
 return None
 try:
 payload = jwt.decode(
 token,
 self.secret_key,
 algorithms=[self.algorithm]
 )
 except (jwt.ExpiredSignatureError, jwt.InvalidTokenError):
 return None
 user_id = payload.get('user_id')
 if not user_id:
 return None
 User = get_user_model()
 try:
 return User.objects.get(pk=user_id)
 except User.DoesNotExist:
 return None

 def get_user(self, user_id):
 User = get_user_model()
 try:
 return User.objects.get(pk=user_id)
 except User.DoesNotExist:
 return None

כניסה חברתית באמצעות django-allauth

אם אתה משתמש ב-FLT:16 עבור אימות חברתי, אתה יכול לעטוף אותו בתוך מפעל בחזרה.דוגמה זו מניחה את זרימת הכניסה החברתית מטופלת על ידי השקפות של הכלות; ה- מפעל אחורי ייקרא לאחר ה- OAuth callback כדי להשלים את הכניסה.

# authfactory/backends.py (continued)
from allauth.socialaccount.models import SocialLogin, SocialAccount
from django.contrib.auth import get_user_model
from .base_auth import BaseAuthMethod

class SocialAuthBackend(BaseAuthMethod):
 def __init__(self, provider):
 self.provider = provider

 def authenticate(self, request):
 # This is called after allauth's social login process.
 # The user object is typically stored in the request by allauth.
 if hasattr(request, 'user') and request.user.is_authenticated:
 return request.user
 # Alternatively, you could inspect the session for a social token.
 return None

 def get_user(self, user_id):
 User = get_user_model()
 try:
 return User.objects.get(pk=user_id)
 except User.DoesNotExist:
 return None

הסגנית הזו פשוטה בכוונה; שילוב מלא יטפל במכונת הכניסה החברתית המנוהלת על ידי האלת'.הנקודה המרכזית היא שכל רצף תואם לממשק של אותו FLT 18.

בית החרושת

מעמד המפעל מחליט מי בדיוק חוזר למידיט.הוא יכול להשתמש שרשרת פשוטה (FLT:19 או מיפוי מילון ליציבות.We'll גם לאפשר תצורה מהגדרות Django.

# authfactory/factory.py
from django.conf import settings
from .backends import (
 UsernamePasswordAuth,
 OAuth2Auth,
 JWTAuth,
 SocialAuthBackend,
)

class AuthMethodFactory:
 """Factory that returns the appropriate authentication backend."""

 _backends = {
 'username_password': UsernamePasswordAuth,
 'oauth2': lambda: OAuth2Auth(
 provider_token_url=settings.OAUTH2_TOKEN_URL,
 userinfo_url=settings.OAUTH2_USERINFO_URL,
 client_id=settings.OAUTH2_CLIENT_ID,
 ),
 'jwt': lambda: JWTAuth(
 secret_key=settings.JWT_SECRET_KEY,
 algorithm=settings.JWT_ALGORITHM,
 ),
 'social': lambda: SocialAuthBackend(provider='google'),
 }

 @classmethod
 def get_backend(cls, method_type, **kwargs):
 """
 Return an instance of the authentication backend
 identified by `method_type`.
 """
 if method_type not in cls._backends:
 raise ValueError(f"Unknown authentication method: {method_type}")
 backend_creator = cls._backends[method_type]
 if callable(backend_creator):
 return backend_creator()
 return backend_creator()

 @classmethod
 def get_backend_names(cls):
 """Return a list of all registered backend names."""
 return list(cls._backends.keys())

יישום זה משתמש במילון של כבשים כדי להחזיר את התשואות המיידיות של עצלות הדורשות טיעונים מבניים.השיטה 21 יכול גם לקבל טענות מילות מפתח נוספות אם אתה צריך להתגבר על הפרמטרים ברירת מחדל עבור בקשה מסוימת (למשל, ספק אחר).

אפילו גמישות גדולה יותר, אתה יכול לאחסן את תצורה backend במסד הנתונים ולהרשם אותם באופן דינמי.עם זאת, מיפוי סטטי הוא לעתים קרובות מספיק וקל יותר לבדיקה.

שימוש במפעל בנוף ובאינטרנט

עכשיו לשלב את המפעל לתוך תצוגות Django שלך.הלקוח (מתחר או API צרכן) חייב לומר לשרת אשר שיטת אימות זה מתכוון להשתמש.זה יכול להיעשות באמצעות פרמטר שאילתה, שדה POST, או מנהל HTTP מותאם אישית.

כניסה מסורתית View

# authfactory/views.py
from django.contrib.auth import login
from django.http import HttpResponse, Http404
from django.views.decorators.csrf import csrf_exempt
import json
from .factory import AuthMethodFactory

@csrf_exempt
def login_view(request):
 """
 Login endpoint that supports multiple authentication methods.
 Expects a JSON body with 'auth_type' and method-specific credentials.
 """
 if request.method != 'POST':
 return HttpResponse(status=405, content='Method not allowed')

 try:
 data = json.loads(request.body)
 except json.JSONDecodeError:
 return HttpResponse(status=400, content='Invalid JSON')

 auth_type = data.get('auth_type', 'username_password')
 try:
 backend = AuthMethodFactory.get_backend(auth_type)
 except ValueError as e:
 return HttpResponse(status=400, content=str(e))

 user = backend.authenticate(request)
 if user is not None:
 login(request, user, backend='django.contrib.auth.backends.ModelBackend')
 return HttpResponse('Login successful')
 else:
 return HttpResponse(status=401, content='Invalid credentials')

הערה: הפונקציה FPLT:23 דורש פרמטר מיתר אחורי.ב יישום אמיתי, אתה יכול לאחסן את הנתיב האחורי במושב או להשתמש המעמד האחורי של המפעל כדי להפיק את הדרך באופן אוטומטי.

תגיות: Django REST Framework

אם אתה חושף API, אתה יכול להתאים את תבנית המפעל לשימוש עם שיעורי אימות של DRF במקום ליצור תצוגה נפרדת, לכתוב שיעור אימות מותאם אישית המנציג למפעל.

# authfactory/rest_auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .factory import AuthMethodFactory

class FactoryBackendAuth(BaseAuthentication):
 """
 DRF authentication class that uses the AuthMethodFactory
 to validate tokens. The 'auth_type' is derived from a custom
 header 'X-Auth-Type'.
 """

 def authenticate(self, request):
 auth_type = request.META.get('HTTP_X_AUTH_TYPE', 'jwt')
 try:
 backend = AuthMethodFactory.get_backend(auth_type)
 except ValueError:
 raise AuthenticationFailed('Unsupported authentication type')

 user = backend.authenticate(request)
 if user is None:
 raise AuthenticationFailed('Invalid token')
 return (user, None)

 def authenticate_header(self, request):
 return 'Bearer' # Generic challenge for any method

לאחר מכן להוסיף את התעודה להגדרות DRF או לתצוגה:

# settings.py
REST_FRAMEWORK = {
 'DEFAULT_AUTHENTICATION_CLASSES': [
 'authfactory.rest_auth.FactoryBackendAuth',
 # other classes can be kept as fallback
 ],
}

המונחים: backend Selection

לפעמים אתה צריך לבחור באופן אוטומטי חזרה על בסיס מאפייני הבקשה (למשל, סוכן משתמשים, IP, דומיין) באפשרותך לכתוב מודעות ביניים שחתופה את הבקשה ומזריקת את החזרה המתאימה ל-FLT:27.

# authfactory/middleware.py
from .factory import AuthMethodFactory

class AutoAuthBackendMiddleware:
 """
 Middleware that selects an authentication backend based on
 the request path or host.
 """
 def __init__(self, get_response):
 self.get_response = get_response

 def __call__(self, request):
 # Decide on auth type - example: use 'oauth2' for /api/v2/auth/*
 path = request.path_info
 if path.startswith('/api/v2/auth/'):
 request.auth_type = 'oauth2'
 elif path.startswith('/api/v1/auth/'):
 request.auth_type = 'jwt'
 else:
 request.auth_type = 'username_password'
 return self.get_response(request)

ניתן להשתמש בדעות שלכם (FLT:29) מבלי לדרוש מהלקוח לציין זאת.

שיקולים מתקדמים

קידוד וטעויות

מערכות אימות הייצור צריכות כניסה חזקה.הוספת כניסה מובנה בתוך כל גבה והמפעל כדי ללכוד ניסיונות אימות, כישלונות ואירועים ביטחוניים פוטנציאליים.

import logging
logger = logging.getLogger(__name__)

class JWTAuth(BaseAuthMethod):
 def authenticate(self, request):
 # ... validation ...
 if not token:
 logger.warning('JWT auth attempted with no token')
 return None
 try:
 payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
 except jwt.ExpiredSignatureError:
 logger.info('Expired JWT token')
 return None
 except jwt.InvalidTokenError:
 logger.warning('Invalid JWT token')
 return None
 # ... user retrieval ...
 if user is None:
 logger.error(f'JWT valid but user {payload.get("user_id")} not found')
 return user

בדיקה במפעל ובחזרה

כל גב צריך להיבדק בבידוד.לנצל את לקוח המבחן של דאנגגו או לבקשות ללעג.עבור המפעל, לבדוק כי הוא מחזיר את הסוג הנכון לכל שיטה רשומה מעלה את ה-FLT:31 עבור לא ידוע.

# tests/test_auth.py
from django.test import TestCase
from unittest.mock import Mock
from authfactory.factory import AuthMethodFactory
from authfactory.backends import UsernamePasswordAuth, OAuth2Auth

class FactoryTest(TestCase):
 def test_get_username_password_backend(self):
 backend = AuthMethodFactory.get_backend('username_password')
 self.assertIsInstance(backend, UsernamePasswordAuth)

 def test_get_oauth2_backend(self):
 backend = AuthMethodFactory.get_backend('oauth2')
 self.assertIsInstance(backend, OAuth2Auth)

 def test_unknown_method_raises_error(self):
 with self.assertRaises(ValueError):
 AuthMethodFactory.get_backend('unknown')

class UsernamePasswordAuthTest(TestCase):
 def test_authenticate_with_valid_credentials(self):
 # Create a test user
 from django.contrib.auth import get_user_model
 User = get_user_model()
 user = User.objects.create_user(username='test', password='secret')
 # ... mock request ...
 request = Mock()
 request.POST = {'username': 'test', 'password': 'secret'}
 backend = UsernamePasswordAuth()
 result = backend.authenticate(request)
 self.assertEqual(result, user)

הקמת המערכת

כדי להוסיף שיטת אימות חדשה (למשל, SAML, קישור קסם, WebAuthn), אתה רק צריך:

  1. "הקים" (ב"ב) "וישנו" (בראשית כ"ד) ו"ה' (ב"ב)" (בראשית י"ד).
  2. שם הספר בלועזית : The Bourn in the FLT:36
  3. באופן אופציונלי להוסיף כניסה לתצורה בהגדרות Django.

טביעת הרגל המינימלית הזו הופכת את המערכת לקלה לתחזוקה ולמבחן.You יכול גם לארוז כל גב כאפליקציית נפרדת.

היתרונות של שימוש בתבנית החרושת עבור Authentication

  • (ב) ⁇ :0) ,[דרוש מקור]: כל שיטת אימות היא מנבאת במעמדה, מה שהופך את בסיס הקוד לקל יותר לנווט והסיבה לכך.
  • (FLT:0) שקיפות: הוספת אסטרטגיה חדשה של אימות אינה דורשת שינויים בהשקפות קיימות, כתובות URL או לוגיקה עסקית.
  • (FLT:0) פתח/סגור את עקרון ה- Principle:cioFLT:1) תשתית אימות הליבה סגורה לשינוי אך פתוחה להרחבה באמצעות כיתות אחוריות חדשות.
  • (ה)הספקות:0) ניתן להוכיח את ה-Backends באופן עצמאי.
  • (FLT:0)Configurability: FLT:1 המפעל יכול להיות מונע על ידי הגדרות, רשומות מסד נתונים, או פרמטרים של זמן ריצה, המאפשר סביבות פריסה שונות להשתמש בשיטות אימות שונות.
  • (ב) ⁇ :0) ⁇ של חששות: לוגיקה של אותנטיות 1 הוסרה מהשקפות, מה שהופך את הנוף לקל יותר וממוקד יותר בטיפול לבקשה.

מסקנה

עיצוב מערכת אימות מודולרית עם דפוס שיטת המפעל בדנגו הופך את הפיסת מונוליטית מסורתית של תשתיות לתוך מרכיב גמיש, נמרץ, על ידי הגדרת ממשק מופשט ו backends קונקרטי עבור כל אסטרטגיה אימות, אתה מקבל את היכולת להחליף או להוסיף שיטות אימות ללא נגיעה בשאר היישום שלך.השיעור המרכזי של לוגיקה מיידית, ואת התבנית משתלבת בצורה חלקה עם מסגרות DjangoRF, אימותים צד שלישי, וספריות DANG.

גישה זו אינה מוגבלת לאימות; אותה תבנית של שיטת המפעל ניתן ליישם בתחומים אחרים של פרויקט Django שלך, כגון שער תשלום, ערוצי התראה או יבוא נתונים.כפי שהיישומים שלך גדל, התבנית עוזרת לך לשמור על גבולות נקיים ושומר על בסיס הקוד שלך להסתגל לדרישות עתידיות.

לקריאה נוספת על שיטות אופטימליות ב Django, להתייעץ עם ה-FLT:0 ; אותנטיות רשמית של Django אימות אותנטיות FLT:1 ; כדי לחקור אימות מתקדם יותר מבוסס אסימונים, ראה את ה-FLT:2Simple JWT מדריך עבור Drémple JWT עבור DrépLT 3 ; וכדי לצלול עמוק לתוך תבניות עיצוב, FLT:4factoring של שיטת המפעל של Gurus: