← Назад к вопросам
Как сделать кастомную авторизацию в Django?
2.0 Middle🔥 171 комментариев
#Django#Безопасность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Кастомная авторизация в Django
Джанго имеет гибкую систему аутентификации. Вот как создать свою кастомную реализацию.
1. Встроенная система Django
Сначала поймем встроенную систему:
# models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
"""Кастомная модель пользователя"""
email = models.EmailField(unique=True)
phone = models.CharField(max_length=20, blank=True)
is_verified = models.BooleanField(default=False)
USERNAME_FIELD = 'email' # Использовать email вместо username
REQUIRED_FIELDS = ['username'] # Обязательные поля при создании
def __str__(self):
return self.email
# settings.py
AUTH_USER_MODEL = 'myapp.CustomUser' # Указать кастомную модель
2. Кастомный Backend для аутентификации
# auth_backends.py
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
import re
User = get_user_model()
class EmailBackend(ModelBackend):
"""Аутентификация по email вместо username"""
def authenticate(self, request, username=None, password=None, **kwargs):
try:
# Сначала пытаемся найти по email
user = User.objects.get(email=username)
except User.DoesNotExist:
# Потом по username (для совместимости)
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
return None
# Проверяем пароль
if user.check_password(password) and self.user_can_authenticate(user):
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
class PhoneBackend(ModelBackend):
"""Аутентификация по телефону"""
def authenticate(self, request, phone=None, password=None, **kwargs):
try:
user = User.objects.get(phone=phone)
except User.DoesNotExist:
return None
if user.check_password(password) and self.user_can_authenticate(user):
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
# settings.py
AUTHENTICATION_BACKENDS = [
'myapp.auth_backends.EmailBackend',
'myapp.auth_backends.PhoneBackend',
'django.contrib.auth.backends.ModelBackend', # Встроенный
]
3. Использование кастомного backend
# views.py
from django.contrib.auth import authenticate, login
from django.views.decorators.http import require_POST
from django.shortcuts import render, redirect
@require_POST
def login_view(request):
email = request.POST.get('email')
password = request.POST.get('password')
# Аутентификация через EmailBackend
user = authenticate(request, username=email, password=password)
if user is not None:
login(request, user) # Создать сессию
return redirect('dashboard')
else:
return render(request, 'login.html', {'error': 'Invalid credentials'})
@require_POST
def login_by_phone(request):
phone = request.POST.get('phone')
password = request.POST.get('password')
# Аутентификация через PhoneBackend
user = authenticate(request, phone=phone, password=password)
if user is not None:
login(request, user)
return redirect('dashboard')
else:
return render(request, 'login.html', {'error': 'Invalid credentials'})
4. Token-based аутентификация (для API)
# models.py
from django.db import models
from django.contrib.auth import get_user_model
import secrets
User = get_user_model()
class AuthToken(models.Model):
"""Токены для API авторизации"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
token = models.CharField(max_length=256, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField()
is_active = models.BooleanField(default=True)
@staticmethod
def generate_token():
return secrets.token_urlsafe(32)
def __str__(self):
return f"{self.user.email} - {self.token[:20]}..."
# auth_backends.py
from rest_framework.authentication import TokenAuthentication
from rest_framework.exceptions import AuthenticationFailed
from datetime import datetime, timezone
class ExpiringTokenAuthentication(TokenAuthentication):
"""Token авторизация с истечением"""
keyword = 'Token'
model = AuthToken
def get_model(self):
return AuthToken
def authenticate_credentials(self, key):
"""Проверить токен и его срок действия"""
try:
token = AuthToken.objects.select_related('user').get(token=key)
except AuthToken.DoesNotExist:
raise AuthenticationFailed('Invalid token.')
if not token.user.is_active:
raise AuthenticationFailed('User inactive.')
if not token.is_active:
raise AuthenticationFailed('Token inactive.')
# Проверка срока действия
if datetime.now(timezone.utc) > token.expires_at:
token.delete()
raise AuthenticationFailed('Token expired.')
return (token.user, token)
# views.py
from rest_framework.decorators import api_view, authentication_classes
from rest_framework.response import Response
from rest_framework.status import HTTP_200_OK, HTTP_401_UNAUTHORIZED
from rest_framework.authtoken.models import Token
from django.contrib.auth import authenticate
from datetime import datetime, timedelta, timezone
@api_view(['POST'])
def login_api(request):
"""Получить токен по email/пароль"""
email = request.data.get('email')
password = request.data.get('password')
user = authenticate(request, username=email, password=password)
if user:
# Удалить старый токен если существует
AuthToken.objects.filter(user=user).delete()
# Создать новый токен
token = AuthToken.objects.create(
user=user,
token=AuthToken.generate_token(),
expires_at=datetime.now(timezone.utc) + timedelta(days=30)
)
return Response({
'token': token.token,
'expires_at': token.expires_at.isoformat(),
'user': {
'id': user.id,
'email': user.email,
'first_name': user.first_name,
}
}, status=HTTP_200_OK)
return Response({'error': 'Invalid credentials'}, status=HTTP_401_UNAUTHORIZED)
@api_view(['GET'])
@authentication_classes([ExpiringTokenAuthentication])
def profile_api(request):
"""Получить профиль (требует токена)"""
user = request.user
return Response({
'id': user.id,
'email': user.email,
'phone': user.phone,
'is_verified': user.is_verified,
})
5. JWT авторизация (более современный способ)
# Установить: pip install djangorestframework-simplejwt
# settings.py
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
}
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}
# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view()), # Получить токены
path('api/token/refresh/', TokenRefreshView.as_view()), # Обновить access token
]
# views.py
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def profile(request):
"""Защищенный endpoint - требует JWT токена"""
user = request.user
return Response({
'id': user.id,
'email': user.email,
'first_name': user.first_name,
})
6. OAuth2 (интеграция с Google, GitHub)
# Установить: pip install python-decouple django-allauth
# settings.py
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'allauth.socialaccount.providers.github',
]
SOCIALACCOUNT_PROVIDERS = {
'google': {
'SCOPE': [
'profile',
'email',
],
'AUTH_PARAMS': {
'access_type': 'online',
},
'APP': {
'client_id': os.getenv('GOOGLE_CLIENT_ID'),
'secret': os.getenv('GOOGLE_CLIENT_SECRET'),
}
}
}
# urls.py
from django.urls import path, include
urlpatterns = [
path('accounts/', include('allauth.urls')),
]
# template
<a href="{% provider_login_url 'google' %}">Sign in with Google</a>
7. 2FA (двухфакторная авторизация)
# models.py
from django_otp.models import Device
from django_otp.plugins.otp_totp.models import StaticDevice, StaticToken
from django_otp.util import random_hex
import pyotp
class UserTwoFactor(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
is_enabled = models.BooleanField(default=False)
secret_key = models.CharField(max_length=32, blank=True)
backup_codes = models.JSONField(default=list)
def generate_secret(self):
self.secret_key = pyotp.random_base32()
return self.secret_key
def verify_token(self, token):
"""Проверить код из Google Authenticator"""
totp = pyotp.TOTP(self.secret_key)
return totp.verify(token)
def generate_backup_codes(self):
"""Сгенерировать коды восстановления"""
codes = [random_hex(8) for _ in range(10)]
self.backup_codes = codes
return codes
# views.py
from django.http import JsonResponse
from django.views.decorators.http import require_POST
@require_POST
def enable_2fa(request):
"""Включить двухфакторную авторизацию"""
user = request.user
# Создать или получить 2FA
two_fa, created = UserTwoFactor.objects.get_or_create(user=user)
# Сгенерировать секретный ключ
secret = two_fa.generate_secret()
# Сгенерировать коды восстановления
backup_codes = two_fa.generate_backup_codes()
two_fa.save()
# Создать QR код
totp = pyotp.TOTP(secret)
qr_uri = totp.provisioning_uri(
name=user.email,
issuer_name='MyApp'
)
return JsonResponse({
'qr_uri': qr_uri,
'backup_codes': backup_codes,
'secret': secret
})
@require_POST
def verify_2fa(request):
"""Проверить код 2FA при логине"""
user = request.user
token = request.POST.get('token')
try:
two_fa = UserTwoFactor.objects.get(user=user)
# Проверить код
if two_fa.verify_token(token):
login(request, user)
return JsonResponse({'success': True})
# Проверить код восстановления
if token in two_fa.backup_codes:
two_fa.backup_codes.remove(token)
two_fa.save()
login(request, user)
return JsonResponse({'success': True, 'warning': 'Backup code used'})
except UserTwoFactor.DoesNotExist:
pass
return JsonResponse({'error': 'Invalid token'}, status=400)
8. Сравнение подходов
┌──────────────┬──────────┬────────────┬─────────────┬────────────┐
│ Подход │ Сложность│ Безопасность│ Масштабируемость│ API │
├──────────────┼──────────┼────────────┼─────────────┼────────────┤
│ Session │ Низкая │ Хорошо │ Среднее │ Не подходит│
│ Token (REST) │ Средняя │ Хорошо │ Хорошее │ Подходит │
│ JWT │ Средняя │ Отличная │ Отличное │ Идеально │
│ OAuth2 │ Высокая │ Отличная │ Отличное │ Идеально │
│ 2FA │ Высокая │ Отличная │ Хорошее │ Опционально│
└──────────────┴──────────┴────────────┴─────────────┴────────────┘
9. Best Practices
# ✅ ХОРОШО: Никогда не логируй пароли
def login_view(request):
email = request.POST.get('email')
password = request.POST.get('password')
# НЕ делай: logger.info(f"Login attempt: {email}, {password}")
logger.info(f"Login attempt: {email}") # Только email
# ✅ ХОРОШО: Rate limiting для защиты от brute force
from django_ratelimit.decorators import ratelimit
@ratelimit(key='ip', rate='5/m') # 5 попыток в минуту
@require_POST
def login_view(request):
pass
# ✅ ХОРОШО: Хешировать пароли при сохранении
user = User.objects.create_user(
email='alice@example.com',
password='secure_password' # Django автоматически хеширует
)
# ❌ ПЛОХО: Сохранять пароли в plain text
user.password = 'plaintext_password'
user.save()
# ✅ ХОРОШО: Использовать HTTPS для всех логинов
if not request.is_secure():
return redirect('https://...')
# ✅ ХОРОШО: Set secure cookie flags
SESSION_COOKIE_SECURE = True # Только HTTPS
SESSION_COOKIE_HTTPONLY = True # Не доступен из JS
SESSION_COOKIE_SAMESITE = 'Strict' # CSRF защита
10. Полный пример: Email + Password + JWT
# models.py
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
email = models.EmailField(unique=True)
USERNAME_FIELD = 'email'
# auth_backends.py
class EmailBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = CustomUser.objects.get(email=username)
except CustomUser.DoesNotExist:
return None
if user.check_password(password):
return user
return None
# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView
from . import views
urlpatterns = [
path('api/login/', views.CustomTokenObtainPairView.as_view()),
path('api/profile/', views.profile_view),
]
# views.py
from rest_framework_simplejwt.views import TokenObtainPairView
class CustomTokenObtainPairView(TokenObtainPairView):
"""Login с email вместо username"""
pass
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def profile_view(request):
user = request.user
return Response({'email': user.email, 'id': user.id})
Заключение
Для кастомной авторизации в Django:
- Session - для традиционных веб-приложений
- Token - для простого API
- JWT - для современного API (рекомендуется)
- OAuth2 - для социальных логинов
- 2FA - для повышенной безопасности
Всегда помни о безопасности: HTTPS, rate limiting, хеширование паролей!