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!
Merhaba Egehan,
Firebase token’ını django’ya nasıl onaylatabiliriz(sanırım arka planda django nun firebase ile etkileşime girip kullanıcıdan gelen token’ların doğruluğunu kontrol etmesi gerekir, bu yapıyı django da nasıl oluşturabiliriz)?
Teşekkürler.
İyi günler.