Anpassa autentiseringen i Django¶
Den autentisering som följer med Django är tillräckligt bra för de flesta vanliga fall, men du kan ha behov som inte uppfylls av standardinställningarna. Att anpassa autentiseringen i dina projekt kräver förståelse för vilka punkter i det tillhandahållna systemet som är utbyggbara eller utbytbara. Detta dokument ger detaljer om hur autentiseringssystemet kan anpassas.
Authentication backends tillhandahåller ett utbyggbart system för när ett användarnamn och lösenord som lagras med användarmodellen behöver autentiseras mot en annan tjänst än Djangos standard.
Du kan ge dina modeller custom permissions som kan kontrolleras genom Djangos auktoriseringssystem.
Du kan utöka standardmodellen User
, eller ersätta en helt anpassad modell.
Andra autentiseringskällor¶
Det kan finnas tillfällen då du behöver ansluta till en annan autentiseringskälla - det vill säga en annan källa med användarnamn och lösenord eller autentiseringsmetoder.
Ditt företag kanske redan har en LDAP-installation som lagrar ett användarnamn och lösenord för varje anställd. Det skulle vara krångligt för både nätverksadministratören och användarna själva om användarna hade separata konton i LDAP och de Django-baserade applikationerna.
För att hantera situationer som denna låter Djangos autentiseringssystem dig koppla in andra autentiseringskällor. Du kan åsidosätta Djangos standarddatabasbaserade system, eller så kan du använda standardsystemet i kombination med andra system.
Se authentication backend reference för information om de autentiseringsbackends som ingår i Django.
Ange backend för autentisering¶
Bakom kulisserna upprätthåller Django en lista över ”autentiseringsbackends” som den kontrollerar för autentisering. När någon anropar django.contrib.auth.authenticate()
– som beskrivs i Hur man loggar in en användare – försöker Django autentisera över alla sina autentiseringsbackends. Om den första autentiseringsmetoden misslyckas försöker Django med den andra, och så vidare, tills alla backends har försökts.
Listan över autentiseringsbackends som ska användas anges i inställningen AUTHENTICATION_BACKENDS
. Detta bör vara en lista med Python-sökvägsnamn som pekar på Python-klasser som vet hur man autentiserar. Dessa klasser kan finnas var som helst på din Python-sökväg.
Som standard är AUTHENTICATION_BACKENDS
inställd på:
["django.contrib.auth.backends.ModelBackend"]
Det är den grundläggande autentiseringsbackend som kontrollerar Djangos användardatabas och frågar efter de inbyggda behörigheterna. Det ger inte skydd mot brute force-attacker via någon hastighetsbegränsningsmekanism. Du kan antingen implementera din egen hastighetsbegränsningsmekanism i en anpassad autentiseringsbackend eller använda de mekanismer som tillhandahålls av de flesta webbservrar.
Ordningen på AUTHENTICATION_BACKENDS
spelar roll, så om samma användarnamn och lösenord är giltigt i flera backends kommer Django att stoppa bearbetningen vid den första positiva matchningen.
Om en backend ger upphov till ett PermissionDenied
undantag, kommer autentiseringen omedelbart att misslyckas. Django kommer inte att kontrollera de backends som följer.
Observera
När en användare har autentiserat lagrar Django vilken backend som användes för att autentisera användaren i användarens session, och återanvänder samma backend under hela sessionen när åtkomst till den aktuella autentiserade användaren behövs. Detta innebär i praktiken att autentiseringskällor cachelagras per session, så om du ändrar AUTHENTICATION_BACKENDS
måste du rensa bort sessionsdata om du vill tvinga användare att autentisera sig på nytt med olika metoder. Ett enkelt sätt att göra det är att köra Session.objects.all().delete()
.
Skriva en backend för autentisering¶
En autentiseringsbackend är en klass som implementerar två obligatoriska metoder: get_user(user_id)
och authenticate(request, **credentials)
, samt en uppsättning valfria behörighetsrelaterade auktoriseringsmetoder.
Metoden get_user
tar ett user_id
- som kan vara ett användarnamn, databas-ID eller vad som helst, men som måste vara primärnyckeln i ditt användarobjekt - och returnerar ett användarobjekt eller None
.
Metoden authenticate
tar ett request
-argument och referenser som nyckelordsargument. För det mesta kommer det att se ut så här:
from django.contrib.auth.backends import BaseBackend
class MyBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
# Check the username/password and return a user.
...
Men det kan också autentisera en token, så här:
from django.contrib.auth.backends import BaseBackend
class MyBackend(BaseBackend):
def authenticate(self, request, token=None):
# Check the token and return a user.
...
I vilket fall som helst bör authenticate()
kontrollera de autentiseringsuppgifter den får och returnera ett användarobjekt som matchar dessa autentiseringsuppgifter om autentiseringsuppgifterna är giltiga. Om de inte är giltiga bör den returnera None
.
request
är en HttpRequest
och kan vara None
om den inte tillhandahölls till authenticate()
(som skickar den vidare till backend).
Djangos admin är tätt kopplad till Djangos User-objekt. För att en användare ska få åtkomst till admin måste till exempel User.is_staff
och User.is_active
vara True
(se AdminSite.has_permission()
för detaljer).
Det bästa sättet att hantera detta är att skapa ett Django User
-objekt för varje användare som finns för din backend (t.ex. i din LDAP-katalog, din externa SQL-databas etc.) Du kan antingen skriva ett skript för att göra detta i förväg, eller så kan din authenticate
-metod göra det första gången en användare loggar in.
Här är ett exempel på en backend som autentiserar mot en användarnamn- och lösenordsvariabel som definieras i din `settings.py
-fil och skapar ett Django User
-objekt första gången en användare autentiserar. I det här exemplet är det skapade Django User
-objektet en superanvändare som kommer att ha full tillgång till admin:
from django.conf import settings
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User
class SettingsBackend(BaseBackend):
"""
Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
Use the login name and a hash of the password. For example:
ADMIN_LOGIN = 'admin'
ADMIN_PASSWORD = 'pbkdf2_sha256$30000$Vo0VlMnkR4Bk$qEvtdyZRWTcOsCnI/oQ7fVOu1XAURIZYoOZ3iq8Dr4M='
"""
def authenticate(self, request, username=None, password=None):
login_valid = settings.ADMIN_LOGIN == username
pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
if login_valid and pwd_valid:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
# Create a new user. There's no need to set a password
# because only the password from settings.py is checked.
user = User(username=username) # is_active defaults to True.
user.is_staff = True
user.is_superuser = True
user.save()
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Anpassade behörigheter¶
För att skapa anpassade behörigheter för ett visst modellobjekt använder du permissions
model Meta attribute.
Detta exempel på en Task
-modell skapar två anpassade behörigheter, dvs. åtgärder som användare kan eller inte kan göra med Task
-instanser, som är specifika för din applikation:
class Task(models.Model):
...
class Meta:
permissions = [
("change_task_status", "Can change the status of tasks"),
("close_task", "Can remove a task by setting its status as closed"),
]
Det enda detta gör är att skapa dessa extra behörigheter när du kör manage.py migrate
(funktionen som skapar behörigheter är kopplad till post_migrate
-signalen). Din kod ansvarar för att kontrollera värdet på dessa behörigheter när en användare försöker komma åt den funktionalitet som tillhandahålls av applikationen (ändra status för uppgifter eller stänga uppgifter.) Om vi fortsätter med exemplet ovan kontrollerar följande om en användare kan stänga uppgifter:
user.has_perm("app.close_task")
Utökning av den befintliga User
-modellen¶
Det finns två sätt att utöka standardmodellen User
utan att ersätta den med en egen modell. Om de ändringar du behöver är rent beteendemässiga och inte kräver någon ändring av vad som lagras i databasen kan du skapa en :ref:proxymodell <proxy-models>
baserad på User
. Detta möjliggör alla de funktioner som erbjuds av proxymodeller, inklusive standardbeställning, anpassade chefer eller anpassade modellmetoder.
Om du vill lagra information som är relaterad till User
kan du använda en OneToOneField
till en modell som innehåller fälten för ytterligare information. Denna en-till-en-modell kallas ofta en profilmodell, eftersom den kan lagra icke-auth-relaterad information om en webbplatsanvändare. Du kan till exempel skapa en Employee-modell:
from django.contrib.auth.models import User
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.CharField(max_length=100)
Om vi utgår från en befintlig anställd Fred Smith som har både en användar- och en anställdmodell kan du komma åt den relaterade informationen med hjälp av Djangos standardkonventioner för relaterade modeller:
>>> u = User.objects.get(username="fsmith")
>>> freds_department = u.employee.department
För att lägga till en profilmodells fält på användarsidan i admin, definiera en InlineModelAdmin
(för detta exempel använder vi en StackedInline
) i din apps admin.py` och lägg till den i en ``UserAdmin
-klass som är registrerad med User
-klassen:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from my_user_profile_app.models import Employee
# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class EmployeeInline(admin.StackedInline):
model = Employee
can_delete = False
verbose_name_plural = "employee"
# Define a new User admin
class UserAdmin(BaseUserAdmin):
inlines = [EmployeeInline]
# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
Dessa profilmodeller är inte speciella på något sätt - de är bara Django-modeller som råkar ha en en-till-en-länk med en användarmodell. Som sådana skapas de inte automatiskt när en användare skapas, men en django.db.models.signals.post_save
kan användas för att skapa eller uppdatera relaterade modeller på lämpligt sätt.
Att använda relaterade modeller resulterar i ytterligare frågor eller joins för att hämta de relaterade uppgifterna. Beroende på dina behov kan en anpassad användarmodell som innehåller de relaterade fälten vara ett bättre alternativ, men befintliga relationer till standardanvändarmodellen i projektets appar kan motivera den extra databasbelastningen.
Ersätta en anpassad User
-modell¶
Vissa typer av projekt kan ha autentiseringskrav för vilka Djangos inbyggda User
-modell inte alltid är lämplig. På vissa webbplatser är det till exempel mer meningsfullt att använda en e-postadress som din identifieringstoken istället för ett användarnamn.
Django låter dig åsidosätta standardanvändarmodellen genom att tillhandahålla ett värde för inställningen AUTH_USER_MODEL
som refererar till en anpassad modell:
AUTH_USER_MODEL = "myapp.MyUser"
Detta prickade par beskriver label
för Django-appen (som måste finnas i din INSTALLED_APPS
) och namnet på den Django-modell som du vill använda som din användarmodell.
Använda en anpassad användarmodell när du startar ett projekt¶
Om du startar ett nytt projekt kan du ställa in en anpassad användarmodell som beter sig identiskt med standardanvändarmodellen genom att underklassa AbstractUser
:
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
Glöm inte att peka AUTH_USER_MODEL
till den. Gör detta innan du skapar några migreringar eller kör manage.py migrate
för första gången.
Registrera också modellen i appens admin.py
:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
admin.site.register(User, UserAdmin)
Byte till en anpassad användarmodell mitt i projektet¶
Det är möjligt att ändra AUTH_USER_MODEL
efter att du har skapat databastabeller, men det kan vara komplicerat eftersom det påverkar t.ex. utländska nycklar och många-till-många-relationer.
Den här ändringen kan inte göras automatiskt och kräver att du manuellt fixar ditt schema, flyttar dina data från den gamla användartabellen och eventuellt manuellt tillämpar vissa migreringar igen. Se #25313 för en översikt över stegen.
På grund av begränsningar i Djangos dynamiska beroendefunktion för utbytbara modeller måste den modell som refereras till av AUTH_USER_MODEL
skapas i den första migreringen av dess app (vanligtvis kallad 0001_initial
); annars får du beroendeproblem.
Dessutom kan du stöta på ett CircularDependencyError
när du kör dina migreringar eftersom Django inte automatiskt kan bryta beroendeslingan på grund av det dynamiska beroendet. Om du ser det här felet bör du bryta slingan genom att flytta de modeller som din användarmodell är beroende av till en andra migrering. (Du kan prova att skapa två normala modeller som har en ForeignKey
till varandra och se hur makemigrations
löser det cirkulära beroendet om du vill se hur det vanligtvis görs)
Återanvändbara appar och AUTH_USER_MODEL
¶
Återanvändbara appar bör inte implementera en anpassad användarmodell. Ett projekt kan använda många appar, och två återanvändbara appar som implementerade en anpassad användarmodell kunde inte användas tillsammans. Om du behöver lagra information per användare i din app, använd en ForeignKey
eller OneToOneField
till settings.AUTH_USER_MODEL
enligt beskrivningen nedan.
Hänvisning till modellen User
¶
Om du refererar till User
direkt (t.ex. genom att hänvisa till den i en främmande nyckel), kommer din kod inte att fungera i projekt där AUTH_USER_MODEL
har ändrats till en annan användarmodell.
- get_user_model()[source]¶
Istället för att referera till
User
direkt, bör du referera till användarmodellen meddjango.contrib.auth.get_user_model()
. Denna metod returnerar den för närvarande aktiva användarmodellen - den anpassade användarmodellen om en sådan anges, ellerUser
annars.När du definierar en främmande nyckel eller många-till-många-relationer till användarmodellen bör du ange den anpassade modellen med hjälp av inställningen
AUTH_USER_MODEL
. Till exempel:from django.conf import settings from django.db import models class Article(models.Model): author = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, )
När du ansluter till signaler som skickas av användarmodellen bör du ange den anpassade modellen med hjälp av inställningen
AUTH_USER_MODEL
. Till exempel:from django.conf import settings from django.db.models.signals import post_save def post_save_receiver(sender, instance, created, **kwargs): pass post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL)
Generellt sett är det enklast att hänvisa till användarmodellen med inställningen
AUTH_USER_MODEL
i kod som körs vid importtillfället, men det är också möjligt att anropaget_user_model()
medan Django importerar modeller, så du kan användamodels.ForeignKey(get_user_model(), ...)
.Om din app testas med flera användarmodeller, till exempel med hjälp av
@override_settings(AUTH_USER_MODEL=...)
och du cachar resultatet avget_user_model()
i en variabel på modulnivå, kan du behöva lyssna påsetting_changed
-signalen för att rensa cacheminnet. Till exempel:from django.apps import apps from django.contrib.auth import get_user_model from django.core.signals import setting_changed from django.dispatch import receiver @receiver(setting_changed) def user_model_swapped(*, setting, **kwargs): if setting == "AUTH_USER_MODEL": apps.clear_cache() from myapp import some_module some_module.UserModel = get_user_model()
Ange en anpassad användarmodell¶
När du börjar ditt projekt med en anpassad användarmodell bör du fundera på om det är rätt val för ditt projekt.
Om du samlar all användarrelaterad information i en modell behöver du inte göra fler eller mer komplexa databasfrågor för att hämta relaterade modeller. Å andra sidan kan det vara mer lämpligt att lagra appspecifik användarinformation i en modell som har en relation till din anpassade användarmodell. Det gör att varje app kan specificera sina egna krav på användardata utan att potentiellt motverka eller bryta mot antaganden från andra appar. Det innebär också att du håller din användarmodell så enkel som möjligt, fokuserad på autentisering och följer de minimikrav som Django förväntar sig att anpassade användarmodeller ska uppfylla.
Om du använder standardbackend för autentisering måste din modell ha ett enda unikt fält som kan användas för identifiering. Detta kan vara ett användarnamn, en e-postadress eller något annat unikt attribut. Ett icke-unikt användarnamnsfält är tillåtet om du använder en anpassad autentiseringsbackend som kan stödja det.
Det enklaste sättet att konstruera en kompatibel anpassad användarmodell är att ärva från AbstractBaseUser
. AbstractBaseUser
tillhandahåller kärnimplementeringen av en användarmodell, inklusive hashade lösenord och tokeniserade lösenordsåterställningar. Du måste sedan tillhandahålla några viktiga implementeringsdetaljer:
- class models.CustomUser¶
- USERNAME_FIELD¶
En sträng som beskriver namnet på det fält i användarmodellen som används som unik identifierare. Detta är vanligtvis ett användarnamn av något slag, men det kan också vara en e-postadress eller någon annan unik identifierare. Fältet måste vara unikt (t.ex. ha
unique=True
inställt i sin definition), såvida du inte använder en anpassad autentiseringsbackend som kan stödja icke-unika användarnamn.I följande exempel används fältet
identifier
som identifieringsfält:class MyUser(AbstractBaseUser): identifier = models.CharField(max_length=40, unique=True) ... USERNAME_FIELD = "identifier"
- EMAIL_FIELD¶
En sträng som beskriver namnet på e-postfältet i modellen
User
. Detta värde returneras avget_email_field_name()
.
- REQUIRED_FIELDS¶
En lista över de fältnamn som kommer att efterfrågas när en användare skapas via hanteringskommandot
createsuperuser
. Användaren kommer att uppmanas att ange ett värde för vart och ett av dessa fält. Det måste inkludera alla fält för vilkablank
ärFalse
eller odefinierat och kan inkludera ytterligare fält som du vill bli ombedd att ange när en användare skapas interaktivt.REQUIRED_FIELDS
har ingen effekt i andra delar av Django, som att skapa en användare i admin.Här är till exempel den partiella definitionen för en användarmodell som definierar två obligatoriska fält - födelsedatum och längd:
class MyUser(AbstractBaseUser): ... date_of_birth = models.DateField() height = models.FloatField() ... REQUIRED_FIELDS = ["date_of_birth", "height"]
Observera
REQUIRED_FIELDS
måste innehålla alla obligatoriska fält i din användarmodell, men bör inte innehållaUSERNAME_FIELD
ellerpassword
eftersom dessa fält alltid kommer att efterfrågas.
- is_active¶
Ett boolean-attribut som anger om användaren anses vara ”aktiv”. Detta attribut tillhandahålls som ett attribut på
AbstractBaseUser
med standardvärdetTrue
. Hur du väljer att implementera det beror på detaljerna i dina valda auth-backends. Se dokumentationen av attributetis_active på den inbyggda användarmodellen
för detaljer.
- get_full_name()¶
Valfritt. En längre formell identifierare för användaren, t.ex. deras fullständiga namn. Om det implementeras visas detta tillsammans med användarnamnet i ett objekts historik i
django.contrib.admin
.
- get_short_name()¶
Valfritt. En kort, informell identifierare för användaren, t.ex. användarens förnamn. Om det implementeras ersätter detta användarnamnet i hälsningen till användaren i rubriken för
django.contrib.admin
.
Importera
AbstractBaseUser
AbstractBaseUser
ochBaseUserManager
är importerbara fråndjango.contrib.auth.base_user
så att de kan importeras utan att inkluderadjango.contrib.auth
iINSTALLED_APPS
.
Följande attribut och metoder är tillgängliga för alla subklasser av AbstractBaseUser
:
- class models.AbstractBaseUser¶
- get_username()¶
Returnerar värdet på det fält som anges av
USERNAME_FIELD
.
- clean()¶
Normaliserar användarnamnet genom att anropa
normalize_username()
. Om du åsidosätter den här metoden, se till att anropasuper()
för att behålla normaliseringen.
- classmethod get_email_field_name()¶
Returnerar namnet på det e-postfält som anges av attributet
EMAIL_FIELD
. Standardvärdet är'email'
omEMAIL_FIELD
inte har angetts.
- classmethod normalize_username(username)¶
Tillämpar NFKC Unicode-normalisering på användarnamn så att visuellt identiska tecken med olika Unicode-kodpunkter betraktas som identiska.
- is_authenticated¶
Skrivskyddat attribut som alltid är
True
(i motsats tillAnonymousUser.is_authenticated
som alltid ärFalse
). Det här är ett sätt att se om användaren har autentiserats. Det innebär inte några behörigheter och kontrollerar inte om användaren är aktiv eller har en giltig session. Även om du normalt kommer att kontrollera detta attribut pårequest.user
för att ta reda på om det har fyllts i avAuthenticationMiddleware
(som representerar den för närvarande inloggade användaren), bör du veta att detta attribut ärTrue
för allaUser
-instanser.
- is_anonymous¶
Skrivskyddat attribut som alltid är
False
. Detta är ett sätt att skilja mellan objektenUser
ochAnonymousUser
. Generellt sett bör du föredra att användais_authenticated
framför detta attribut.
- set_password(raw_password)¶
Ställer in användarens lösenord till den angivna råsträngen och tar hand om lösenordshashningen. Sparar inte
AbstractBaseUser
-objektet.När raw_password är
None
kommer lösenordet att sättas till ett oanvändbart lösenord, som omset_unusable_password()
hade använts.
- check_password(raw_password)¶
- acheck_password(raw_password)¶
Asynkron version:
acheck_password()
Returnerar
True
om den angivna råsträngen är rätt lösenord för användaren. (Detta tar hand om lösenordets hashing vid jämförelsen.)
- set_unusable_password()¶
Markerar att användaren inte har något lösenord angivet. Detta är inte samma sak som att ha en tom sträng som lösenord.
check_password()
för den här användaren kommer aldrig att returneraTrue
. Sparar inte objektetAbstractBaseUser
.Du kan behöva detta om autentiseringen för din applikation sker mot en befintlig extern källa, t.ex. en LDAP-katalog.
- has_usable_password()¶
Returnerar
False
omset_unusable_password()
har anropats för den här användaren.
- get_session_auth_hash()¶
Returnerar en HMAC för lösenordsfältet. Används för Inaktivering av session vid byte av lösenord.
- get_session_auth_fallback_hash()¶
Ger HMAC för lösenordsfältet med hjälp av
SECRET_KEY_FALLBACKS
. Används avget_user()
.
AbstractUser
subclasses AbstractBaseUser
:
- class models.AbstractUser¶
- clean()¶
Normaliserar e-postmeddelandet genom att anropa
BaseUserManager.normalize_email()
. Om du åsidosätter den här metoden, se till att anropasuper()
för att behålla normaliseringen.
Skriva en manager för en anpassad användarmodell¶
Du bör också definiera en anpassad manager för din användarmodell. Om din användarmodell definierar fälten username
, email
, is_staff
, is_active
, is_superuser
, last_login
och date_joined
på samma sätt som Djangos standardanvändare, kan du installera Djangos UserManager
; men om din användarmodell definierar olika fält måste du definiera en anpassad manager som utökar BaseUserManager
med ytterligare två metoder:
- class models.CustomUserManager¶
- create_user(username_field, password=None, **other_fields)¶
Prototypen för
create_user()
bör acceptera användarnamnsfältet plus alla obligatoriska fält som argument. Om din användarmodell till exempel använderemail
som användarnamnsfält och hardate_of_birth
som ett obligatoriskt fält, börcreate_user
definieras som:def create_user(self, email, date_of_birth, password=None): # create user here ...
- create_superuser(username_field, password=None, **other_fields)¶
Prototypen för
create_superuser()
bör acceptera användarnamnsfältet plus alla obligatoriska fält som argument. Om din användarmodell till exempel använderemail
som användarnamnsfält och hardate_of_birth
som ett obligatoriskt fält, börcreate_superuser
definieras som:def create_superuser(self, email, date_of_birth, password=None): # create superuser here ...
För en ForeignKey
i USERNAME_FIELD
eller REQUIRED_FIELDS
får dessa metoder värdet av to_field
(standard primary_key
) i en befintlig instans.
BaseUserManager
tillhandahåller följande verktygsmetoder:
- class models.BaseUserManager¶
- classmethod normalize_email(email)¶
Normaliserar e-postadresser genom att skriva domändelen av e-postadressen med liten bokstav.
- get_by_natural_key(username)¶
- aget_by_natural_key(username)¶
Asynkron version:
aget_by_natural_key()
Hämtar en användarinstans med hjälp av innehållet i det fält som anges av
USERNAME_FIELD
.Changed in Django 5.2:metoden
aget_by_natural_key()
har lagts till.
Utökning av Djangos förvalda User
¶
Om du är helt nöjd med Djangos User
-modell, men du vill lägga till ytterligare profilinformation, kan du underordna django.contrib.auth.models.AbstractUser
och lägga till dina egna profilfält, även om vi rekommenderar en separat modell som beskrivs i Ange en anpassad användarmodell. AbstractUser
ger den fullständiga implementeringen av standard User
som en abstrakt modell.
Anpassade användare och de inbyggda autentiseringsformulären¶
Djangos inbyggda forms och views gör vissa antaganden om den användarmodell som de arbetar med.
Följande formulär är kompatibla med alla underklasser av AbstractBaseUser
:
AuthenticationForm
: Använder användarnamnsfältet som anges avUSERNAME_FIELD
.
Följande formulär innehåller antaganden om användarmodellen och kan användas som de är om dessa antaganden uppfylls:
PasswordResetForm
: Antar att användarmodellen har ett fält som lagrar användarens e-postadress med det namn som returneras avget_email_field_name()
(email
som standard) som kan användas för att identifiera användaren och ett booleskt fält med namnetis_active
för att förhindra återställning av lösenord för inaktiva användare.
Slutligen är följande formulär knutna till User
och måste skrivas om eller utökas för att fungera med en anpassad användarmodell:
Om din anpassade användarmodell är en underklass till AbstractUser
kan du utöka dessa formulär på följande sätt:
from django.contrib.auth.forms import UserCreationForm
from myapp.models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = UserCreationForm.Meta.fields + ("custom_field",)
Anpassade användare och django.contrib.admin
¶
Om du vill att din anpassade användarmodell även ska fungera med administratören måste din användarmodell definiera ytterligare några attribut och metoder. Dessa metoder gör det möjligt för administratören att kontrollera användarens åtkomst till administratörsinnehåll:
- class models.CustomUser
- is_staff¶
Returnerar
True
om användaren har rätt att få åtkomst till administratörssidan.
- is_active¶
Returnerar
True
om användarkontot för närvarande är aktivt.
- has_perm(perm, obj=None):
Returnerar
True
om användaren har den angivna behörigheten. Omobj
anges måste behörigheten kontrolleras mot en specifik objektinstans.
- has_module_perms(app_label):
Returnerar
True
om användaren har behörighet att komma åt modeller i den angivna appen.
Du måste också registrera din anpassade användarmodell hos administratören. Om din anpassade användarmodell utökar django.contrib.auth.models.AbstractUser
kan du använda Djangos befintliga django.contrib.auth.admin.UserAdmin
-klass. Men om din användarmodell utökar AbstractBaseUser`
, måste du definiera en anpassad ModelAdmin
klass. Det kan vara möjligt att underordna standardklassen django.contrib.auth.admin.UserAdmin
; du måste dock åsidosätta alla definitioner som hänvisar till fält på django.contrib.auth.models.AbstractUser
som inte finns i din anpassade användarklass.
Observera
Om du använder en anpassad ModelAdmin
som är en underklass av django.contrib.auth.admin.UserAdmin
, måste du lägga till dina anpassade fält till fieldsets
(för fält som ska användas vid redigering av användare) och till add_fieldsets
(för fält som ska användas när du skapar en användare). Till exempel:
from django.contrib.auth.admin import UserAdmin
class CustomUserAdmin(UserAdmin):
...
fieldsets = UserAdmin.fieldsets + ((None, {"fields": ["custom_field"]}),)
add_fieldsets = UserAdmin.add_fieldsets + ((None, {"fields": ["custom_field"]}),)
Se ett fullständigt exempel för mer information.
Anpassade användare och behörigheter¶
För att göra det enkelt att inkludera Djangos behörighetsramverk i din egen användarklass tillhandahåller Django PermissionsMixin
. Detta är en abstrakt modell som du kan inkludera i klasshierarkin för din användarmodell, vilket ger dig alla metoder och databasfält som krävs för att stödja Djangos behörighetsmodell.
PermissionsMixin
tillhandahåller följande metoder och attribut:
- class models.PermissionsMixin¶
- is_superuser¶
Boolesk. Anger att den här användaren har alla behörigheter utan att uttryckligen ha tilldelats dem.
- get_user_permissions(obj=None)¶
Returnerar en uppsättning behörighetssträngar som användaren har direkt.
Om
obj
anges returneras endast användarrättigheterna för detta specifika objekt.
- get_group_permissions(obj=None)¶
Returnerar en uppsättning behörighetssträngar som användaren har via sina grupper.
Om
obj
anges returneras endast gruppbehörigheterna för detta specifika objekt.
- get_all_permissions(obj=None)¶
Returnerar en uppsättning behörighetssträngar som användaren har, både genom grupp- och användarbehörigheter.
Om
obj
anges returneras endast behörigheterna för detta specifika objekt.
- has_perm(perm, obj=None)¶
Returnerar
True
om användaren har den angivna behörigheten, därperm
är i formatet"<app label>.<permission codename>"
(se permissions <topic-authorization>`). OmUser.is_active
ochis_superuser
båda ärTrue
returnerar denna metod alltidTrue
.Om
obj
skickas in kommer denna metod inte att söka efter en behörighet för modellen, utan för detta specifika objekt.
- has_perms(perm_list, obj=None)¶
Returnerar
True
om användaren har alla de angivna behörigheterna, där varje behörighet är i formatet"<app label>.<permission codename>"
. OmUser.is_active
ochis_superuser
båda ärTrue
returnerar denna metod alltidTrue
.Om
obj
anges kommer den här metoden inte att kontrollera behörigheter för modellen, utan för det specifika objektet.
- has_module_perms(package_name)¶
Returnerar
True
om användaren har några behörigheter i det angivna paketet (Django-appens etikett). OmUser.is_active
ochis_superuser
båda ärTrue
, returnerar denna metod alltidTrue
.
Anpassade användare och proxymodeller¶
En begränsning med anpassade användarmodeller är att installation av en anpassad användarmodell kommer att bryta alla proxymodeller som utökar User
. Proxymodeller måste baseras på en konkret basklass; genom att definiera en anpassad användarmodell tar du bort Djangos förmåga att på ett tillförlitligt sätt identifiera basklassen.
Om ditt projekt använder proxymodeller måste du antingen ändra proxyn för att utöka den användarmodell som används i ditt projekt, eller slå samman din proxys beteende i din User
-underklass.
Ett fullständigt exempel¶
Här är ett exempel på en anpassad användarapp som är kompatibel med administratörer. Denna användarmodell använder en e-postadress som användarnamn och har ett obligatoriskt födelsedatum; den ger ingen behörighetskontroll utöver en admin
-flagga på användarkontot. Den här modellen skulle vara kompatibel med alla inbyggda auth-formulär och -vyer, förutom formulären för att skapa användare. Detta exempel illustrerar hur de flesta komponenterna fungerar tillsammans, men är inte avsett att kopieras direkt till projekt för produktionsanvändning.
Denna kod skulle alla leva i en models.py
-fil för en anpassad autentiseringsapp:
from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
class MyUserManager(BaseUserManager):
def create_user(self, email, date_of_birth, password=None):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not email:
raise ValueError("Users must have an email address")
user = self.model(
email=self.normalize_email(email),
date_of_birth=date_of_birth,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, date_of_birth, password=None):
"""
Creates and saves a superuser with the given email, date of
birth and password.
"""
user = self.create_user(
email,
password=password,
date_of_birth=date_of_birth,
)
user.is_admin = True
user.save(using=self._db)
return user
class MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name="email address",
max_length=255,
unique=True,
)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = MyUserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["date_of_birth"]
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
@property
def is_staff(self):
"Is the user a member of staff?"
# Simplest possible answer: All admins are staff
return self.is_admin
För att sedan registrera denna anpassade användarmodell med Djangos admin krävs följande kod i appens fil admin.py
:
from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError
from customauth.models import MyUser
class UserCreationForm(forms.ModelForm):
"""A form for creating new users. Includes all the required
fields, plus a repeated password."""
password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
password2 = forms.CharField(
label="Password confirmation", widget=forms.PasswordInput
)
class Meta:
model = MyUser
fields = ["email", "date_of_birth"]
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
"""A form for updating users. Includes all the fields on
the user, but replaces the password field with admin's
disabled password hash display field.
"""
password = ReadOnlyPasswordHashField()
class Meta:
model = MyUser
fields = ["email", "password", "date_of_birth", "is_active", "is_admin"]
class UserAdmin(BaseUserAdmin):
# The forms to add and change user instances
form = UserChangeForm
add_form = UserCreationForm
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference specific fields on auth.User.
list_display = ["email", "date_of_birth", "is_admin"]
list_filter = ["is_admin"]
fieldsets = [
(None, {"fields": ["email", "password"]}),
("Personal info", {"fields": ["date_of_birth"]}),
("Permissions", {"fields": ["is_admin"]}),
]
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
# overrides get_fieldsets to use this attribute when creating a user.
add_fieldsets = [
(
None,
{
"classes": ["wide"],
"fields": ["email", "date_of_birth", "password1", "password2"],
},
),
]
search_fields = ["email"]
ordering = ["email"]
filter_horizontal = []
# Now register the new UserAdmin...
admin.site.register(MyUser, UserAdmin)
# ... and, since we're not using Django's built-in permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)
Slutligen anger du den anpassade modellen som standardanvändarmodell för ditt projekt med hjälp av inställningen AUTH_USER_MODEL
i din settings.py
:
AUTH_USER_MODEL = "customauth.MyUser"
Lägga till ett async-gränssnitt¶
För att optimera prestanda när den anropas från en asynkron kontextautentisering kan backends implementera asynkrona versioner av varje funktion - aget_user(user_id)
och aauthenticate(request, **credentials)
. När en autentiseringsbackend utökar BaseBackend
och async-versioner av dessa funktioner inte tillhandahålls, kommer de automatiskt att syntetiseras med sync_to_async
. Detta har performance penalties.
Medan ett asynkront gränssnitt är valfritt, krävs alltid ett synkront gränssnitt. Det finns ingen automatisk syntes för ett synkront gränssnitt om ett asynkront gränssnitt är implementerat.
Djangos out-of-the-box autentiseringsbackends har inbyggt async-stöd. Om dessa inbyggda backends utökas, var särskilt noga med att se till att async-versionerna av modifierade funktioner också modifieras.