Django – #18 – System Autoryzacji cz. 6

Wprowadzenie.

W szóstej części poświęconej systemowi autoryzacji zajmę się przedstawieniem w jaki sposób szybko zrealizować dostęp do konkretnych stron tylko dla zalogowanych użytkowników, natomiast pozostali (anonimowi) użytkownicy zostaną przekierowani na stronę logowania oraz zostaną poinformowani, że dostęp do danej strony jest możliwy tylko dla zalogowanych użytkowników.

Zakres artykułu.

  • Dostęp do stron dla uwierzytelnionych użytkowników
  • Test

Dostęp do stron dla uwierzytelnionych użytkowników

W celu zrealizowania właściwości uwierzytelniania użytkowników stworzymy trzy dodatkowe strony. Pierwsza strona będzie stroną publiczną w związku z czym wszyscy użytkownicy będą mieli do niej dostęp. W przypadku drugiej i trzeciej strony, dostęp będzie ograniczony dla niezalogowanych użytkowników. Wykonamy również mechanizm, który będzie przekierowywał do strony logowania, gdy niezalogowany użytkownik będzie próbował uzyskać dostęp do strony z wymaganym uwierzytelnieniem.

Pierwszą modyfikację wprowadzimy w pliku urls.py. W tym oto pliku dodamy trzy ścieżki do trzech stron: „publicpage/„, „privatepage/” oraz „privateclasspage/”. 

from django.contrib import admin
from django.urls import path
from auth_system import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
    path('signup', views.signup_page, name='signup_page'),
    path('login', views.login_page, name='login_page'),
    path('logout', views.logout_page, name='logout_page'),

    path('publicpage', views.public_page, name='public_page'),
    path('privatepage', views.private_page, name='private_page'),
    path('privateclasspage', views.PrivateClass_page.as_view(), name='privateclass_page'),
]

W kolejnym kroku w pliku views.py dodamy dwie funkcje oraz jedną klasę, które będą jedynie renderować wskazany plik .html.

from django.views import View

def public_page(request):
    return render(request, 'auth_system/publicpage.html')

def private_page(request):
    return render(request, 'auth_system/privatepage.html')

class PrivateClass_page(View):
    def get(self, request):
        return render(request, 'auth_system/privateclasspage.html')

Kolejno, stworzymy trzy nowe szablony .html o nazwach zgodnych z tymi co dodaliśmy krok wcześniej przy zwracanej funkcji renderowania.

Szablon są uproszone, lecz pozwalają na prostą identyfikację danej konkretnej strony.

{% extends 'auth_system/base.html' %}

{% block content %}
  <div class="container">
    <h2>PUBLIC PAGE</h2>
  </div>
{% endblock %}

{% extends 'auth_system/base.html' %}

{% block content %}
  <div class="container">
    <h2>PRIVATE PAGE</h2>
  </div>
{% endblock %}

{% extends 'auth_system/base.html' %}

{% block content %}
  <div class="container">
    <h2>PRIVATE CLASS PAGE</h2>
  </div>
{% endblock %}

W celu szybkiej nawigacji po nowo stworzonych stronach, zmodyfikujmy szablon strony domowej „home„, dodając do niej trzy przyciski, które będą przekierowywać nas do nowo stworzonych stron.

{% extends 'auth_system/base.html' %}

{% block content %}
  <div class="container">
    <h2>HOME</h2>
    {{ userStatus }}<br>

    <div class="btn-group" role="group" aria-label="Basic example">
      <a href="{% url 'public_page' %}"><button type="button" class="btn btn-primary">Public</button></a>
      <a href="{% url 'private_page' %}"><button type="button" class="btn btn-success">Private</button></a>
      <a href="{% url 'privateclass_page' %}"><button type="button" class="btn btn-warning">Private Class</button></a>
    </div>

  </div>
{% endblock %}

Na tą chwilę mamy trzy nowe strony, do których dostęp nie jest ograniczony w żaden sposób.

Django udostępnia nam mechanizmy dzięki, którym w prosty sposób jesteśmy w stanie zrealizować ograniczenie dla niezalogowanych użytkowników. Ta modyfikacja dokonywana jest w pliku naszych widoków views.py. W tym celu w przypadku funkcji musimy zaimportować dekorator @login_required z from django.contrib.auth.decorators import login_required, natomiast w przypadku klas musimy zaimportować klasę LoginRequiredMixin z from django.contrib.auth.mixins import LoginRequiredMixin. Więcej informacji znajdziemy pon tym linkiem.

def public_page(request):
    return render(request, 'auth_system/publicpage.html')

@login_required
def private_page(request):
    return render(request, 'auth_system/privatepage.html')

class PrivateClass_page(LoginRequiredMixin, View):
    def get(self, request):
        return render(request, 'auth_system/privateclasspage.html')

Kolejną właściwością jaką chcemy osiągnąć w naszej aplikacji jest to, aby w przypadku, gdy użytkownik nie jest zalogowany, a próbuje uzyskać dostęp do strony z wymaganym uwierzytelnieniem, został automatycznie przeniesiony na stronę logowania. W tym celu w pliku settings.py Dodajemy zmienną LOGIN_URL i wprowadzamy ścieżkę do naszej strony logowania. LOGIN_URL = '/login’.

LOGIN_URL = '/login'

W tej chwili, gdy niezalogowany użytkownik chce uzyskać dostęp do strony z wymaganym uwierzytelnieniem, zostaje automatycznie przeniesiony do strony logowania. W przypadku, gdy ten użytkownik po przekierowaniu na stronę logowania zaloguje się to automatycznie zostanie przeniesiony na stronę domową „home” zgodnie z tym co jest w widoku funkcji login_page(). Nas w tym przypadku interesuje jednak takie zachowanie aplikacji, abyśmy zostali przekierowani do strony do której chcieliśmy się dostać. W związku z tym musimy wprowadzić kilka modyfikacji. 

Gdy zostaniemy przekierowani przez mechanizmy Django do strony logowania (przekierowanie realizowane jest metodą GET), wówczas Django do parametrów dodaje nam parametr next, który przechowuje bezwzględną ścieżkę do strony, do której chcieliśmy uzyskać dostęp. Dzięki temu parametrowi możemy w prosty sposób stworzyć warunek, że jeżeli występuje parametr next, wówczas do renderowania szablonu logowania dodamy zmienną informującą o tym, że tylko zalogowani użytkownicy mają dostęp do tej strony oraz dodatkowo musimy tą bezwzględną ścieżkę zamieścić w formularzu tak, aby przy wysłaniu formularzu ze strony logowania zachować informację o bezwzględnej ścieżce do naszej docelowej strony.

def login_page(request):
    context = {}
    if request.method == 'POST':
        ...
    else:
        if request.GET.get('next'):
            context['next'] = 'Tylko zalogowani użytkonicy mają dostęp do tej strony! Zaloguj się.'
            context['nextURL'] = request.GET.get('next')
        ...

Przed dalszą modyfikacją funkcji widoku login_page przejdziemy do modyfikacji szablonu strony logowania. Przekazując powyższe parametry next oraz nextURL możemy teraz odpowiednio dostosować szablon logowania w taki sposób, że jeżeli parametr next wystąpi, wówczas pokażemy informację, że dostęp mają jedynie zalogowani użytkownicy.

{% extends 'auth_system/base.html' %}

{% block content %}
  <div class="container">
    <h2>LOGIN</h2><br>

    ...
    {% if next %}
      <div class="alert alert-warning">
        {{ next }}<br>
      </div>
    {% endif %}

    <form method="POST">
      ...
    </form>
  </div>
{% endblock %}

Drugą modyfikację wprowadzimy w formularzu, gdzie dodamy dodatkowe pole, jeżeli występuje parametr nextURL. Pole to będzie niewidoczne dla użytkownika, będzie nosiło nazwę redir i będzie przechowywać adres bezwzględnej ścieżki do docelowej strony, tak aby podczas wysyłania formularza informacja ta nam nie uciekła.

{% extends 'auth_system/base.html' %}

{% block content %}
  <div class="container">
    <h2>LOGIN</h2><br>

    ...

    <form method="POST">
      ...

      {% if nextURL %}
        <input type="hidden" name="redir" value="{{ nextURL }}">
      {% endif %}

      ...
    </form>
  </div>
{% endblock %}

Cały kod szablonu logowania prezentuje się następująco:

{% extends 'auth_system/base.html' %}

{% block content %}
  <div class="container">
    <h2>LOGIN</h2><br>

    {% if error %}
      <div class="alert alert-danger">
        {{ error }}<br>
      </div>
    {% endif %}
    {% if next %}
      <div class="alert alert-warning">
        {{ next }}<br>
      </div>
    {% endif %}

    <form method="POST">
      {% csrf_token %}
      Nazwa użytkownika:<br>
      <input type="text" name="username"><br><br>
      Hasło:<br>
      <input type="password" name="password"><br><br>

      {% if nextURL %}
        <input type="hidden" name="redir" value="{{ nextURL }}">
      {% endif %}

      <input class="btn btn-primary" type="submit" value="Zaloguj">
    </form>
  </div>
{% endblock %}

W tym momencie możemy wrócić do funkcji widoku login_page. W warunku gdzie mamy metodę POST i gdzie mamy uwierzytelnionego użytkownika dodajemy kolejny warunek, który będzie sprawdzał, czy w parametrach występuje parametr redir, który dodaliśmy krok wcześniej. Jeżeli parametr ten będzie występował, wówczas oznacza to, że przekierowanie należy zwrócić na ścieżkę, która zawarta jest w tym parametrze.

def login_page(request):
    context = {}
    if request.method == 'POST':
        user = auth.authenticate(username=request.POST['username'] ,password=request.POST['password'])
        if user is not None:
            auth.login(request, user)
            if request.POST.get('redir'):
                return redirect(f"{request.POST.get('redir')}")
            else:
                return redirect('home')
        else:
            ...
    else:
        ...

Cały kod funkcji widoku login_page po obydwu modyfikacjach prezentuje się następująco:

def login_page(request):
    context = {}
    if request.method == 'POST':
        user = auth.authenticate(username=request.POST['username'] ,password=request.POST['password'])
        if user is not None:
            auth.login(request, user)
            if request.POST.get('redir'):
                return redirect(f"{request.POST.get('redir')}")
            else:
                return redirect('home')
        else:
            context['error'] = 'Podane hasło lub login są błędne! Podaj poprawne dane.'
            if request.POST.get('redir'):
                context['next'] = 'Tylko zalogowani użytkonicy mają dostęp do tej strony! Zaloguj się.'
                context['nextURL'] = request.GET.get('next')
            return render(request, 'auth_system/login.html', context)
    else:
        if request.GET.get('next'):
            context['next'] = 'Tylko zalogowani użytkonicy mają dostęp do tej strony! Zaloguj się.'
            context['nextURL'] = request.GET.get('next')
        return render(request, 'auth_system/login.html', context)

Dostęp do stron dla uwierzytelnionych użytkowników

Przejdźmy teraz do przetestowania naszej aplikacji. W pierwszej kolejności wprowadźmy adres 127.0.0.1:8000. W efekcie powinniśmy zobaczyć następujący widok.

W pierwszej kolejności naciśnijmy przycisk Public

Strona powinna załadować się bez problemu.

Następnie wróćmy na stronę domową i naciśnijmy przycisk Private.

W efekcie powinniśmy zostać przekierowani na stronę do logowania i powinniśmy zobaczyć komunikat „Tylko zalogowani użytkownicy mają dostęp do tej strony! Zaloguj się.”. 

Po wpisaniu poprawnych danych do logowania powinniśmy zobaczyć stronę na której znajduje się napis „PRIVATE PAGE„. Czy jesteśmy zalogowani możemy w prosty sposób zobaczyć, poprzez inny wygląd paska nawigacyjnego, który wykonaliśmy w poprzednim wpisie o Systemie Autoryzacji to jest w części piątej. 

Kolejnym testem jaki wykonamy jest wpisanie niepoprawnych danych. W tym celu wylogujmy się z aplikacji, przejdźmy do strony home i ponownie naciśnijmy przycisk „Private„. Gdy znajdziemy się na stronie logowanie wprwadźmy niepoprawne dane.

W rezultacie powinniśmy otrzymać następujący efekt, gdzie na stronie mamy dwie informacje. Jedna informacja mówi nam, że dostęp do strony jest jedynie dla zalogowanych użytkowników, natomiast druga informacja mówi nam, że wprowadziliśmy nieprawidłowe dane. Wprowadźmy teraz poprawne dane i zobaczymy jaki otrzymamy efekt.

Jak widzimy po poprawnym wpisaniu danych, przekierowywani jesteśmy ponownie na naszą docelową stronę, czyli wszystko działa poprawnie.

Na koniec dla formalności możemy sprawdzić, czy taki sam efekt otrzymamy po naciśnięciu przycisku „Private Class„. 

Autor artykułu
Dominik Bednarski

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *