Přejít k navigační liště

Zdroják » Různé » Django: Autentizace a autorizace

Django: Autentizace a autorizace

Články Různé

Občas potřebujeme nějaký obsah zpřístupnit pouze určité sortě návštěvníků. Abychom návštěvníky odlišili, musí se autentizovat pomocí identifikačních údajů, což může být například uživatelského jméno a heslo. Django má v sobě zabudovanou podporu takovéto autentizace a také máme možnost autorizovat přístup pomocí přístupových práv a skupin.

Na začátek si neodpustím terminologickou poznámku: autentizace (někdy psáno též jako autentikace, případně autentifikace) je proces ověření identity uživatele. Pomocí ní tedy zjistíme, že u počítač pravděpodobně sedí Alice a ne třeba Bob, protože je aktuální návštěvník přihlášen pomocí Aliciných uživatelských údajů. Autorizace se oproti tomu stará o kontrolu oprávnění, tedy aby přístup měli jenom ti uživatelé, kterým jsme ho povolili.

Modul django.contrib.auth

Aplikaci zajišťující autentizaci a autorizaci můžeme nalézt v modulu Djanga django.contrib.auth. Hlavní součástí je model User, který představuje přihlášeného uživatele. Nepřihlášení uživatelé jsou reprezentováni odvozeným modelem AnonymousUser. Ať už je uživatel přihlášen nebo ne, můžeme k jeho modelu přistupovat v pohledech pomocí objektu request.user a v šablonách přes proměnnou {{ user }}. Instance modelu uživatele obsahuje několik zajímavých atributů:

  • user.username: uživatelské jméno
  • user.first_name: křestní jméno
  • user.last_name: příjmení
  • user.email: e-mail
  • user.is_staff: příznak, zda má uživatel povolený vstup do administrace
  • user.is_active: příznak, zda je uživatel aktivní

Kromě atributů máme k dispozici několik důležitých metod:

  • user.is_authenticated(): zkontroluje, zda je uživatel přihlášen
  • user.get_full_name(): celé jméno uživatele
  • user.set_password(): nastaví uživatelovo heslo
  • user.check_password(): ověří, jestli se zadané heslo shoduje s heslem uživatele

Toto není vyčerpávající seznam, popis mnoha dalších atributů a metod lze najít v dokumentaci a některé si ještě představíme později.

Protože je uživatel reprezentován modelem, zacházíme s ním podobně jako s ostatními databázovými modely. Můžeme přidávat nové uživatelské účty, odstraňovat je a upravovat uživatelské údaje. Vytváření nového uživatelského účtu nám může usnadnit funkce create_user:

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('honza', 'honza@example.com', 'honzovoheslo')
>>> user.first_name = 'Jan'
>>> user.last_name = 'Novák'
>>> user.save()
>>> user
<User: honza>
>>> user.is_staff
False
>>> user.password
'sha1$32f0e$c445ff000ed85dff88f32f874d7021bee2fe1cc4'
>>> user.check_password('honzovoheslo')
True
>>> user.check_password('jinéheslo')
False
Hesla nejsou uložena v databázi přímo, ale ukládá se tam pouze jejich otisk, takže pokud chceme uživateli heslo změnit nebo ověřit, musíme použít metodu set_password nebo check_password.

Přihlašování a odhlašování

Identita návštěvníka se dá ověřit pomocí funkce authenticate, která vrátí objekt uživatele v případě úspěšného ověření nebo prázdný objekt pokud se ověření nezdaří. Poté uživatele můžeme přihlásit pomocí metody login a kdykoliv později odhlásit pomocí metody logout. Místo toho, abychom se pustili do vytváření pohledů pro přihlašování a odhlašování, využijeme generické pohledy, které byly pro tento účel vytvořeny. Otevřeme si tedy soubor urls.py a připíšeme na konec několik řádků kódu:

urlpatterns += patterns('',
    # ...
    (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
    (r'^accounts/logout/$', 'django.contrib.auth.views.logout', {'template_name': 'logout.html'}),
)

Cesty /accounts/login/ a /accounts/logout/ jsou výchozí a dají se přenastavit v souboru settings.py pomocí konstant LOGIN_URL a LOGOUT_URL. Klíčem template_name jsem si vybral jiné umístění šablony, ve výchozím nastavení jsou umístěny v podadresáři registration. Šablona pro přihlašování (soubor templates/login.html) může vypadat třeba takto:

{% extends "base.html" %}

{% block content %}
<h1>{% block title %}Přihlášení{% endblock %}</h1>

{% if form.errors %}
  <p>Špatně zadané uživatelské jméno nebo heslo. Zkuste se prosím přihlásit znovu.</p>
{% endif %}

<form action="." method="POST">
<table>
{% include "form.html" %}
<tr>
<td></td>
<td>
<input type="submit" value="Přihlásit se">
<input type="hidden" name="next" value="{{ next }}">
</td>
</tr>
</table>
</form>
{% endblock %}

V případě neúspěšného přihlášení vypíšeme na sedmém řádku chybovou hlášku. Na dvanáctém řádku je využita šablona pro výpis formuláře z minulého dílu. Sedmnáctý řádek obsahuje skrytou hodnotu formuláře next, což je adresa stránky, na kterou budeme po přihlášení přesměrováni. Pokud není hodnota next zadána, standardně se přejde na /accounts/profile/ (dá se přenastavit pomocí konstanty LOGIN_REDIRECT_URL).

Django 9

Odhlašovací šablona (templates/logout.html) bude oproti té přihlašovací jednodušší:

{% extends "base.html" %}

{% block content %}
<h1>{% block title %}Odhlášeno{% endblock %}</h1>

<p>Byl(a) jste odhlášen(a). Nashle příště!</p>
{% endblock %}

Django 9

Uživatelský profil

Na začátku jsme se dozvěděli, že model uživatele obsahuje jeho jméno, příjmení a e-mail. Ale co když chceme k uživatelskému účtu připojit dodatečné informace? Stačí rozšířit jeho model pomocí tzv. profilu. Otevřeme si soubor video_store/models.py a přidáme tento kód:

from django.contrib.auth.models import User

class Profile(models.Model):
    user = models.OneToOneField(User, verbose_name='Uživatel')
    info = models.TextField('Informace', blank=True)

    def __unicode__(self):
        return self.user.username

    class Meta:
        verbose_name = 'profil'
        verbose_name_plural = 'profily'

Založili jsme si nový model, který k modelu uživatele připojí textové pole, kam můžeme poznamenat jakékoliv další údaje. Na čtvrtém řádku je použit atribut typu OneToOneField, což je obdoba unikátního cizího klíče (ForeignKey s parametrem unique=True). Takováto vazba nám zaručí, že jeden uživatel bude mít právě jeden profil (ne více).

Když máme definovaný model, sesynchronizujeme ho s databází (python manage.py syncdb) a vytvoříme pro něj formulář. V tomto případě ho zčásti můžeme vygenerovat. Otevřeme si soubor video_store/forms.py a vložíme do něj následující kód:

from django.contrib.auth.models import User

class ProfileForm(forms.ModelForm):
    info = forms.CharField(label='Informace', widget=forms.Textarea, required=False)
    password1 = forms.CharField(label='Heslo', widget=forms.PasswordInput, required=False)
    password2 = forms.CharField(label='Heslo znovu', widget=forms.PasswordInput, required=False)

    class Meta:
        model = User
        fields = ('first_name', 'last_name', 'email')

    def clean_password2(self):
        if self.cleaned_data.get('password1') != self.cleaned_data['password2']:
            raise forms.ValidationError('Zadaná hesla se liší.')

        return self.cleaned_data['password2']

Můžeme zde vidět dvě nové věci. První je, že formulář neodvozujeme od třídy forms.Form, ale forms.ModelForm. Tato třída slouží pro generování formulářů z modelů. V podtřídě Meta specifikujeme atributem model, ze kterého modelu se má formulář generovat. Pokud neuvedeme výčet atributů modelu, které se mají použít, pomocí meta atributů fields nebo exclude, jsou pro generování použity všechny atributy. Druhá nová věc je metoda clean_password2. Máme možnost si definovat vlastní validaci určitého formulářového políčka pomocí metody pojmenované clean_políčko. Pokud uživatelský vstup nevyhovuje, vyhodí se výjimka forms.ValidationError a u daného políčka se vypíše uživateli chybová hláška. V tomto případě kontrolujeme, zda jsou zadaná hesla stejná.

Ještě musíme provázat uživatelský model s profilem. Na to má Django vyhrazenou konstantu AUTH_PROFILE_MODULE, která se tradičně nachází v souboru settings.py. Stačí do něj připsat tento řádek:

AUTH_PROFILE_MODULE = 'video_store.Profile'

Jakmile má tato konstanta přiřazenou cestu k našemu modelu, stačí u objektu uživatele zavolat metodu get_profile. Tato metoda vrací profil daného uživatele nebo vyvolává výjimku Profile.DoesNotExist. Vytvoříme si v souboru video_store/views.py pohled, ve kterém využijeme tohoto propojení:

from models import Profile
from forms import ProfileForm
from django.contrib.auth.decorators import login_required

@login_required
def profile(request, saved):
    try:
        profile = request.user.get_profile()
    except Profile.DoesNotExist:
        profile = Profile(user=request.user)

    if request.method == 'POST':
        form = ProfileForm(request.POST)

        if form.is_valid():
            request.user.first_name = form.cleaned_data['first_name']
            request.user.last_name = form.cleaned_data['last_name']
            request.user.email = form.cleaned_data['email']

            if len(form.cleaned_data['password1']) > 0:
                request.user.set_password(form.cleaned_data['password1'])

            request.user.save()

            profile.info = form.cleaned_data['info']
            profile.save()

            return HttpResponseRedirect('/accounts/profile/saved/')
    else:
        form = ProfileForm(initial={'first_name': request.user.first_name,
                                    'last_name': request.user.last_name,
                                    'email': request.user.email,
                                    'info': profile.info,
                                    })

    return render_to_response('profile.html', {'form': form, 'saved': saved}, context_instance=RequestContext(request))

Před samotnou definicí pohledu je podivný kód @login_required, kterému se říká dekorátor. Je to vlastně funkce, která obalí jinou funkci, jenom je zde použit zkrácený zápis, který se používá v Pythonu 2.4 a novějším. Tento dekorátor zamezí přístup všem nepřihlášeným uživatelům a přesměruje je na stránku s přihlášením. V samotném pohledu zavoláme již zmíněnou metodu get_profile (řádek 8) a v případě, že uživatel nemá přiřazený profil, založíme mu nový (řádek 10). Do databáze ukládáme zvlášť objekt uživatele a zvlášť jeho profil. Pokud uživatel vyplnil kolonku s heslem (řádek 20), změníme mu ho. Všimněte si, že zobrazený formulář (řádek 30) má některá políčka již předvyplněná.

K pohledu je potřeba vytvořit odpovídající šablonu. V ní stačí zobrazit formulář a v případě, že došlo k jeho úspěšnému uložení, vypsat potvrzující hlášku. Šablonu uložíme do souboru templates/profile.html:

{% extends "base.html" %}

{% block content %}
<h1>{% block title %}Uživatelský profil{% endblock %}</h1>

{% if saved %}
  <p>Profil byl upraven.</p>
{% endif %}

<form action="." method="POST">
<table>
{% include "form.html" %}
<tr>
<td></td>
<td><input type="submit" value="Upravit"></td>
</tr>
</table>
</form>
{% endblock %}

A nakonec si můžeme v souboru templates/base.html rozšířit část <div id="menu"> o zobrazování informací o aktuálně přihlášeném uživateli:

{% if user.is_authenticated %}
<ul id="user">
<li>Přihlášen: <a href="/accounts/profile/">{{ user }}</a></li>
{% if user.is_staff %}
<li><a href="/admin/">Administrace</a></li>
{% endif %}
<li><a href="/accounts/logout/">Odhlásit</a></li>
</ul>
{% endif %}

Django 9

Související odkazy

V dalším díle se budeme zabývat nahráváním souborů do aplikace.

Komentáře

Subscribe
Upozornit na
guest
8 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
yed_

Nebylo by lepsi pouzit pro uzivatelsky profil dedicnost modelu? Mam s tim lepsi zkusenosti, nezli s vazbou pres OneToOneField(). S danym modelem se pak jednoduseji pracuje. Priklad:

from django.contrib­.auth.models import User
from django.contrib­.auth.models import UserManager

class Profile(AuthUser):
info = models.TextFi­eld(_(‚Informa­ce‘), blank=True)

objects = UserManager()

Pavel Lang

V případě šikovného použití dědičnosti nikoliv. V čem mám v Djangu s dědičností modelů problém je vazba přez GenericForeignKey, tam mi to trochu drhlo, ale vyřešilo se to také docela snadno.
Je ale pravda, že zrovna uživatelský profil je radno dělat v Djangu tak jak v článku, v podstatě to už je „konvence“.

Honza Kral

Uvedeny kus kodu vyhodi vyjimku pokud pro password1 neprojde validace – nebude naplnen v cleaned_data a dostanete KeyError.

Bylo by take pekne nepouzivat v prikladech (obzvlast pro zacatecniky) natvrdo zadane URL adresy, ale pouzit tag {% url %} a metodu reverse ve views.

ModelForm neni jen trida ke generovani formularu, obsahuje hlavne logiku, ktera umoznuje i praci s modely – napriklad neni nutne pracne konstruovat initial argument, ale lze do formulare rovnou predat instanci pres parametr instance. Zavolani .save() na tom formulari pak provede patricny update toho modelu aniz byste se museli starat o rucni pridelovani jednotlivych fieldu (bez predani instance do __init__ .save() vytvori novou instanci modelu).

Honza Kral

Pardon, prvni bod neplati, priklad je napsany spravne, moc se omlouvam, prekoukl jsem se.

kiot

Mozna byste mohl v dalsim dile udelat navod na rest hesla. Jedna se o lehky proces, ktery vsak neni nikde poradne popsan.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.