İçeriğe geç

Django Rest Framework ile Token Bazlı Kullanıcı Doğrulaması Nasıl Yapılır?

Herkese merhabalar.

Web uygulamalarının ayrılmaz parçalarından biride kullanıcı doğrulaması yaparak isteği yapan kullanıcının yapmak istediği işleme erişimi olup olmamasını kontrol edip, isteğini reddetmek ya da erişim izni alabilmesi için yönlendirmektir.

Böylece erişimi sınırlandırmış olur ve uygulamamızda daha güvenli bir yapı kurmuş oluruz.


Uygulamanızın birden fazla istemci (mobil uygulama,masaüstü uygulaması vs) ile çalışması gerekiyorsa  burada çalışılabilecek ortak bir taban bulmak daha mantıklı olacaktır.


HTTP Authentication

HTTP protokolü gelen isteklerde kullanıcı doğrulaması yapmak üzere bir mekanizmaya sahiptir. Server gelen isteğin Authorization Header’ını kontrol eder, eksik veya yanlış bilgi var ise 401 Unauthorized  ve desteklediği doğrulama tipini WWW-Authentication header’ı ile kullanıcıyı dönüp bilgilendirir.


Django Rest Framework’de yerleşik olarak 4 farklı kullanıcı doğrulama yöntemi bulunmaktadır.

  • Basic HTTP authentication.
  • Session authentication. (Doğrulama yönetimi Django tarafından yapılır.)
  • Token Authentication.
  • Remote user authentication.

Biz bu yazıda Token authentication üzerinden konuşacağız.


Sanal ortamı aktif edelim ve bağımlılıkları yükleyelim.

virtualenv -p python3 .venv
source .venv/bin/active

pip install "django>=2,<3"
pip install djangorestframework

Daha sonra projemizi oluşturalım ve hesap yönetimi için bir uygulama oluşturalım.

django-admin startproject example . 

./manage.py startapp accounts

example/settings.py

INSTALLED_APPS = [
    # ... built-in django apps
    # 3rd party apps
    "rest_framework",
    "rest_framework.authtoken"
    # my apps
    "accounts.apps.AccountsConfig",
]

Değişiklerimizi migrate etmeden önce Rest Framework Auth Token hakkında konuşalım.

Çalışma mantığı.

Rest framework ile birlikte gelen token authentication’un çalışma mantığı aslında gayet basit.

Her kullanıcı’nın OneToOne ile bağlandığı bir Token modeli bulunmakta. Bu modele kullanıcıların token’ları ve oluşturulma zamanları kaydedilmekte.

Kullancı tokenini elde etmek için istek yaptığında kullanıcı adı ve paroladan oluşan bir serializer sizden veri beklemekte. Veriler alınıp servere POST edildiğinde ilk önce böyle bir kullanıcının gönderdiği veri ile uyuşan bir kullancı olup olmadığı kontrol edilmekte. Eğer kullancı var ise ve token’a sahip değilse oluşturulup, sahipse direkt olarak token dönülmekte. Bu dönen token daha sonra geliştirme yaptığınız istemci tarafında bir alanda saklanıp (örneğin Js frontend frameworkleri için tarayıcının LocalStorage’ı) yapılacak her isteğin HTTP Authorization header‘ına  eklenmekte ve doğrulama yapılmış olmakta.


Basit bir view yazıp kontrol etmek.

accounts/views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

class ExampleView(APIView):
    def get(self, request):
        message = {"foo": "bar"}
        return Response(data=message, status=status.HTTP_200_OK)

/api/ isteklerini accounts/urls.py araması gerektiğini söyleyelim.

example/urls.py

from django.urls import include

urlpatterns = [
    path("api/", include("accounts.urls")),
]

accounts/urls.py dosyasını oluşturun ve şu satırları ekleyin.

from django.urls import path
from accounts import views

urlpatterns = [
    path("example/", views.ExampleView.as_view(), name="example"),
]

Tamam ise serveri koşturun.

./manage.py runserver

Django size uygulanmamış migration’larınız olduğunu söyleyecektir. Pas geçin.

Daha sonra komut satırını açıp HTTP istemciniz ile istek yapın. Ben Httpie kullanıyorum. Curl ‘ de aynı işi görür 🙂

➜  ~ http localhost:8000/api/example/  
HTTP/1.1 200 OK
Allow: GET, HEAD, OPTIONS
Content-Length: 13
Content-Type: application/json
Date: Wed, 08 Apr 2020 14:52:22 GMT
Server: WSGIServer/0.2 CPython/3.6.9
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "foo": "bar"
}

Şimdi de view’ımızı doğrulanmamış kullancılara kapatalım.

Şu 2 sınfı import edip

from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated

ExampleView’ı şu hale getirin.

class ExampleView(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = (IsAuthenticated,)

    def get(self, request):
        message = {"foo": "bar"}
        return Response(data=message, status=status.HTTP_200_OK)

Ve aynı isteği tekrarlayın.

➜  ~ http localhost:8000/api/example/
HTTP/1.1 401 Unauthorized
Allow: GET, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Wed, 08 Apr 2020 15:27:05 GMT
Server: WSGIServer/0.2 CPython/3.6.9
Vary: Accept
WWW-Authenticate: Token
X-Frame-Options: SAMEORIGIN

{
    "detail": "Authentication credentials were not provided."
}

Kimlik doğrulama bilgilerinin verilmediğini söyledi. Ve talep ettiği doğrulamayı belirtti. Tamam görünüyor!


Eğer projenizin başında iseniz kullancı her kayıt olduğunda otomatik olarak Token oluşturmak akıllıca olabilir.

Bu yüzden bizim için her kayıtta otomatik bir token oluşturan bir sinyal yazacağız.

/accounts/signals.py

from django.dispatch import receiver
from django.db.models.signals import post_save
from rest_framework.authtoken.models import Token
from django.contrib.auth import get_user_model


def create_user_token_on_registration(sender, instance, created, **kwargs):
    if created:
        Token.objects.create(user=instance)

post_save.connect(receiver=create_user_token_on_registration, sender=get_user_model())

/accounts/apps.py

from django.apps import AppConfig

class AccountsConfig(AppConfig):
    name = "accounts"

    def ready(self):
        import accounts.signals

Uygulamamız hazır olduğunda sinyalleri içeri aktarmasını belirtiyoruz.

Daha sonra ufak bir test yazacağız ve başarılı ise migration’larımızı veritabanına işleyeceğiz. I love Test Driven Development!

/accounts/tests.py

from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token

class UserTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username="test", password="supersecretpassword",
        )

    def test_created_token_successfully(self):

        token = Token.objects.get(user=self.user)

        self.assertTrue(hasattr(self.user, "auth_token"))

        self.assertEqual(token, self.user.auth_token)

Ufak bir Django testi ile kurulumda bir kullanıcı oluşturuyoruz.  Daha sonra bu kullanıcının tokeni oluşturuldu mu oluşturulduysa doğru mu kontrollerini yapıyoruz.

Daha sonra şu komutu çalıştırıyoruz.

./manage.py test

Her şey yolunda ise artık aşağıdaki iki komutu arka arkaya koşturup veritabanına değişiklikleri işliyoruz.

./manage.py makemigrations && ./manage.py migrate

Son bir adım olarak kullanıcının server’den kendi tokenını’ istemesi kaldı.

Bu iş için Rest Framework Auth Token uygulamasının bir hazır bir view’ı var. Onu kullanacağız. Tabi ki override ederek kişiselleştirebilirsiniz 🙂

/accounts/urls.py

from django.urls import path
from accounts import views
from rest_framework.authtoken.views import ObtainAuthToken

app_name = "accounts"

urlpatterns = [
    path("example/", views.ExampleView.as_view(), name="example"),
    path("obtain-token/", ObtainAuthToken.as_view(), name="obtain-token"),
]

Dosyanın  son halinin böyle gözükmesi gerekmekte. Kullanacağımız view’ı import edip bir url’e bağladık. app_name bizim için Django içinde yapabileceğimiz named views çağrılarında karışıklıkların önüne geçmek için bir prefix olarak kullanılacak ve path’de geçmiş olduğumuz name değişkeni ile birleşecek. Örneğin( accounts:example, accounts:obtain-token)


Biraz daha test 🙂

İlk olarak kullanıcı tokenini isteyebilmeli ve daha sonra HTTP header’ına bu tokeni ekleyip bizim ExampleView’ımızı görebilmeli.

accounts/tests.py

def test_user_obtain_token_successfully_and_authorized(self):
        from rest_framework.test import APIClient
        from django.urls import reverse
        from rest_framework import status

        client = APIClient()

        payload = {"username": "test", "password": "supersecretpassword"}

        token_request = client.post(reverse("accounts:obtain-token"), payload)

        self.assertEqual(token_request.status_code, status.HTTP_200_OK)

        client.credentials(HTTP_AUTHORIZATION=f"Token {self.user.auth_token}")

        example_request = client.get(reverse("accounts:example"))

        self.assertEqual(example_request.status_code, status.HTTP_200_OK)

Kısa olması açısından importları iç içe aldım. Önce bir client oluşturun daha sonra ilk testde oluşturmuş olduğumuz kullanıcı bilgilerini bir payload haline getirdik. Ve post isteği attık. Dönen değerin doğru olduğunu teyit ettikten sonra HTTP Authorization header’ına ekleyin ve ExampleView’ a istek attık. Artık doğrulandığımız için bize izin vermesi gerekmekte.

./manage.py test

 

ile testleri çalıştırın.


Shell ile kullanım.

./manage.py createsuperuser

komutu ile bir kullanıcı oluşturun.

➜./manage.py createsuperuser 
Username: admin
Email address: 
Password: 
Password (again): 
Superuser created successfully.

Token isteği için

➜  ~ http post localhost:8000/api/obtain-token/ username=admin password=qwer1148
HTTP/1.1 200 OK
Allow: POST, OPTIONS
Content-Length: 52
Content-Type: application/json
Date: Wed, 08 Apr 2020 16:18:36 GMT
Server: WSGIServer/0.2 CPython/3.6.9
Vary: Cookie
X-Frame-Options: SAMEORIGIN

{
    "token": "9d91fa9b4aed981f157d0e5018e5fd5fc5a40ed4"
}

Tokenimizi elde ettik.

➜  ~ http localhost:8000/api/example/ 'Authorization: Token 9d91fa9b4aed981f157d0e5018e5fd5fc5a40ed4'
HTTP/1.1 200 OK
Allow: GET, HEAD, OPTIONS
Content-Length: 13
Content-Type: application/json
Date: Wed, 08 Apr 2020 16:22:28 GMT
Server: WSGIServer/0.2 CPython/3.6.9
Vary: Accept
X-Frame-Options: SAMEORIGIN

{
    "foo": "bar"
}

Ve header’ımıza ekleyip ExampleView’ı tekrarladık. Cevap 200 🙂

Toplayacak olursak.

Testlerdi sinyallerdi derken bayağı uzun bir yazı oldu böyle tasarlamamıştım kafamda 🙂

Fakat artık Django Rest Framework ile token bazlı doğrulamanın nasıl yapılabileceğini biliyorsunuz. Bu kullanım gayet hızlı entegre edilebiliyor fakat tabii ki eksileri var her doğrulama işlemi için veritabanına dokunmak zorundasınız. Burada değinilmedi fakat admin panelinde tokenlerinizi görebilirsiniz. Bu admin panelinden yönetilebilecekleri anlamına geliyor.

Son olarak JSON Web Token’ler ile bu işlemi nasıl gerçekleştirebiliriz. Bununla ilgili bir yazıda yazacağım. Görüşmek üzere!

 

 

 

 

Tarih:BlogPython

İlk Yorumu Siz Yapın

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Göster
Gizle