Table of Contents
עיצוב מערכת אימות מודולרית עם דפוס המפעל בדנגו
יישומים מודרניים דורשים מערכות אימות כי הן גמישות ורחבות.משתמשים מצפים להיכנס באמצעות דואר אלקטרוני וסיסמה, חשבונות חברתיים, פרוטוקולים חד-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), אתה רק צריך:
- "הקים" (ב"ב) "וישנו" (בראשית כ"ד) ו"ה' (ב"ב)" (בראשית י"ד).
- שם הספר בלועזית : The Bourn in the FLT:36
- באופן אופציונלי להוסיף כניסה לתצורה בהגדרות 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: