JWT (JSON Web Token) ile Kimlik Doğrulama


Herkese merhaba! Bugün uygulamalarımızın arka planında dönen çok havalı ve bir o kadar da önemli bir konuyu öğreneceğiz: Kimlik Doğrulama (Authentication). Özellikle de modern web ve mobil uygulamaların vazgeçilmezi olan JWT (JSON Web Token) teknolojisini keşfedeceğiz.

Arka uçta (Backend) Django, ön uçta (Frontend) Flutter kullanarak bu sistemi nasıl kuracağımıza adım adım bakacağız. Hazırsanız başlayalım!

JWT (JSON Web Token) Nedir?

Diyelim ki harika bir eğlence parkına gittiniz. Girişte biletinizi veya kimliğinizi gösteriyorsunuz ve size bileğinize takmanız için bir bileklik veriyorlar. Artık parktaki her oyuncağa binerken tekrar tekrar kimlik göstermenize gerek yok; sadece bilekliğinizi göstermeniz yeterli!

İşte JWT tam olarak bu bilekliktir.

  1. Giriş Yaparsınız: Kullanıcı adı ve şifrenizi (kimliğinizi) Django sunucusuna gönderirsiniz.

  2. Bilekliği Alırsınız: Eğer bilgileriniz doğruysa, Django size özel bir JWT (bileklik) oluşturur ve gönderir.

  3. Flutter'da Saklarsınız: Flutter uygulamamız bu bilekliği alır ve telefonun hafızasına (örneğin shared_preferences ile) kaydeder.

  4. İçeriğe Ulaşırsınız: Artık sunucudan özel bir veri istediğinizde (mesela profil bilgileri), şifrenizi değil, bu JWT'yi gönderirsiniz. Django bilekliğe bakar, "Tamam, bu kişiyi tanıyorum" der ve veriyi verir.

JWT'nin Yapısı

Bir JWT, aralarına nokta (.) konulmuş üç parçadan oluşur:

  1. Header (Başlık): Biletin türü ve şifreleme algoritması yazar.

  2. Payload (Yük): Kullanıcının ID'si, yetkileri gibi zararsız bilgiler burada durur.

  3. Signature (İmza): Bu biletin sahte olmadığını, gerçekten Django sunucumuz tarafından üretildiğini kanıtlayan mühürdür.


1. Aşama: Django Sunucusunu Hazırlama (Güvenlik Kapısı)

Öncelikle Django projemize JWT yeteneği kazandırmalıyız. Bunun için djangorestframework-simplejwt paketini kullanacağız.

Adım 1: Kurulum

Terminali açın ve sanal ortamınız aktifken şu komutları yazın:

Bash:
pip install djangorestframework
pip install djangorestframework-simplejwt

Adım 2: Ayarlar (settings.py)

Django'ya bu yeni araçları kullanmasını söylemeliyiz. settings.py dosyanızı açın ve aşağıdaki eklemeleri yapın:

Python:
INSTALLED_APPS = [
    # ... diğer uygulamalarınız
    'rest_framework',
    'rest_framework_simplejwt',
]

# REST Framework'e kimlik doğrulama için JWT kullanacağını söylüyoruz:
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    )
}

Adım 3: Rotaları Belirleme (urls.py)

Kullanıcıların gelip biletlerini (Token) alabilecekleri kapıları (URL'leri) açmamız gerekiyor. Projenizin ana urls.py dosyasını şöyle düzenleyin:

Python:
from django.contrib import admin
from django.urls import path
from rest_framework_simplejwt.views import (
    TokenObtainPairView, # Giriş yapıp token alma görünümü
    TokenRefreshView,    # Süresi biten token'ı yenileme görünümü
)

urlpatterns = [
    path('admin/', admin.site.urls),
    # Flutter'dan kullanıcı adı ve şifre buraya gönderilecek:
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    # Biletin süresi dolarsa yenisini buradan alacağız:
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]

Harika! Artık Django sunucumuz gelen kullanıcı adı ve şifreleri kontrol edip onlara Access (Erişim) ve Refresh (Yenileme) token'ları verebilir duruma geldi.


2. Aşama: Flutter Uygulamasını Hazırlama (Müşteri)

Şimdi Flutter tarafına geçiyoruz. Kullanıcıdan bilgileri alıp Django'ya göndereceğiz, gelen bilekliği (Token) alıp saklayacağız.

Adım 1: Gerekli Paketleri Yükleme

Flutter projenizin terminalinde şu komutu çalıştırarak internete çıkmak için http ve token'ı telefona kaydetmek için shared_preferences paketlerini ekleyin:

Bash:
flutter pub add http shared_preferences

Adım 2: Giriş Yapma Fonksiyonu (Login)

Flutter'da bir servis dosyası oluşturalım. Bu fonksiyon, kullanıcının girdiği bilgileri Django'ya gönderecek.

Dart:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';

class AuthService {
  // Django sunucunuzun adresi (Emülatör için localhost yerine 10.0.2.2 kullanılır)
  final String baseUrl = 'http://10.0.2.2:8000/api';

  Future<bool> login(String username, String password) async {
    // Django'daki token alma adresine gidiyoruz
    final response = await http.post(
      Uri.parse('$baseUrl/token/'),
      body: {
        'username': username,
        'password': password,
      },
    );

    // Eğer sunucu "Tamamdır, bilgiler doğru" (200) derse:
    if (response.statusCode == 200) {
      // Gelen JSON verisini parçala
      var data = jsonDecode(response.body);
      
      // Access token'ı al (Bilekliğimiz)
      String accessToken = data['access'];

      // Token'ı telefonun hafızasına kaydet ki uygulama kapanınca silinmesin
      SharedPreferences prefs = await SharedPreferences.getInstance();
      await prefs.setString('token', accessToken);

      print("Giriş başarılı! Bileklik alındı.");
      return true;
    } else {
      print("Hata: Kullanıcı adı veya şifre yanlış!");
      return false;
    }
  }
}

Adım 3: Bileklikle İçeri Girme (Yetkili İstek Atma)

Diyelim ki girişi yaptık ve token'ı kaydettik. Şimdi Django'dan sadece giriş yapmış kişilerin görebileceği özel bir veri (mesela not defteri veya profil) isteyeceğiz. İstek atarken "Header" (Başlık) kısmına bilekliğimizi iliştirmemiz gerekiyor.

Dart:
  Future<void> getOzelVeriler() async {
    // Önce telefondaki çekmeceden (hafızadan) kaydettiğimiz bilekliği alalım
    SharedPreferences prefs = await SharedPreferences.getInstance();
    String? token = prefs.getString('token');

    if (token == null) {
      print("Önce giriş yapmalısın!");
      return;
    }

    // Şimdi Django'ya istek atıyoruz ama bu sefer bilekliği gösteriyoruz!
    final response = await http.get(
      Uri.parse('$baseUrl/ozel-veriler/'),
      headers: {
        'Authorization': 'Bearer $token', // İşte kritik nokta burası!
      },
    );

    if (response.statusCode == 200) {
      print("Başarılı! Veriler: ${response.body}");
    } else {
      print("Bu veriye ulaşmaya yetkiniz yok veya token süresi dolmuş.");
    }
  }

Özetle Neler Yaptık?

  1. Öğrendik ki JWT, bizi sürekli şifre yazma derdinden kurtaran dijital bir bilekliktir.

  2. Django (Backend) tarafında bu bilekliği üretecek ve kontrol edecek sistemi kurduk.

  3. Flutter (Frontend) tarafında ise kullanıcıdan şifre alıp bilekliği talep ettik ve sonraki işlemlerimiz için bu bilekliği güvenli bir şekilde cebimize (Shared Preferences) koyduk.

Yazılım dünyasında bu sistem, devasa uygulamalardan tutun küçük girişimlere kadar her yerde kullanılır. Siz de kendi projelerinizde bu yapıyı kurarak uygulamalarınızı profesyonel bir seviyeye taşıyabilirsiniz! İyi kodlamalar! 🚀


Öğrencilerinizle laboratuvardaki Pardus makinelerinizde veya kendi bilgisayarlarınızda baştan sona çalıştırabileceğiniz, hem arka ucu hem de ön ucu içeren tam bir "Gizli Notlar" (Secret Notes) uygulaması hazırlayalım.

Bu uygulama, öğrencilerin kendi kullanıcı adları ve şifreleriyle giriş yapıp (JWT biletini alıp), sadece kendilerine ait notları görebilecekleri temel bir sistem olacak.


BÖLÜM 1: Arka Uç (Django)

Öncelikle bir Django projesi (backend) ve bir uygulama (notes_app) oluşturduğunuzu varsayarak en temel dosyaları yazıyoruz. (Önceki mesajdaki JWT ayarlarının settings.py içinde yapıldığını unutmayın).

1. notes_app/models.py (Veritabanı Tasarımı)

Öğrencilerin notlarını tutacağımız tabloyu oluşturuyoruz. Her not, bir kullanıcıya ait olacak.

Python:
from django.db import models
from django.contrib.auth.models import User

class Note(models.Model):
    # Bu not hangi öğrenciye (kullanıcıya) ait?
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=100, verbose_name="Başlık")
    content = models.TextField(verbose_name="Not İçeriği")
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

2. notes_app/serializers.py (Veri Çevirmenimiz)

Veritabanındaki Python nesnelerini, Flutter'ın anlayacağı JSON formatına çeviriyoruz.

Python:
from rest_framework import serializers
from .models import Note

class NoteSerializer(serializers.ModelSerializer):
    class Meta:
        model = Note
        fields = ['id', 'title', 'content', 'created_at']

3. notes_app/views.py (Güvenlik Kontrolü ve İşlemler)

Sadece "bilekliği (JWT Token) olanlar" bu görünüme erişip kendi notlarını görebilir.

Python:
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from .models import Note
from .serializers import NoteSerializer

class NoteListCreate(generics.ListCreateAPIView):
    serializer_class = NoteSerializer
    permission_classes = [IsAuthenticated] # GÜVENLİK KAPISI: Bilekliği olmayan giremez!

    # Sadece giriş yapan kullanıcının kendi notlarını getirir:
    def get_queryset(self):
        return Note.objects.filter(user=self.request.user)

    # Yeni not eklenirken, notun sahibini otomatik olarak giriş yapan kişi yapar:
    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

4. notes_app/urls.py (Rotalar)

Python:
from django.urls import path
from .views import NoteListCreate
from rest_framework_simplejwt.views import TokenObtainPairView

urlpatterns = [
    # Bileklik (Token) Alma Kapısı
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    
    # Notları Görme ve Yazma Kapısı (Bileklik zorunlu)
    path('api/notes/', NoteListCreate.as_view(), name='note_list_create'),
]

(Not: Bu aşamada Django'da python manage.py makemigrations, migrate ve createsuperuser komutlarını çalıştırarak test için bir kullanıcı oluşturmayı unutmayın. Sonrasında python manage.py runserver ile sunucuyu ayağa kaldırın.)


BÖLÜM 2: Ön Uç (Flutter)

Öğrencilerin telefonlarında veya emülatörlerinde göreceği arayüz. http ve shared_preferences paketlerini pubspec.yaml dosyanıza eklediğinizden emin olun.

lib/main.dart








import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
runApp(const GizliNotlarApp());
}

class GizliNotlarApp extends StatelessWidget {
const GizliNotlarApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Gizli Notlar',
theme: ThemeData(primarySwatch: Colors.indigo),
home: const LoginScreen(),
);
}
}

// -------------------------------------------------------------------
// 1. GİRİŞ EKRANI (Bilekliği Alacağımız Yer)
// -------------------------------------------------------------------
class LoginScreen extends StatefulWidget {
const LoginScreen({Key? key}) : super(key: key);

@override
_LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
final TextEditingController kullaniciAdiCtrl = TextEditingController();
final TextEditingController sifreCtrl = TextEditingController();
bool yukleniyor = false;

// Django sunucusunun adresi (Emülatör için 127.0.0.1, Pardus lab ortamında IP adresi yazılabilir)
final String apiUrl = "http://127.0.0.1:8000/api/token/";

Future<void> girisYap() async {
setState(() => yukleniyor = true);

try {
final response = await http.post(
Uri.parse(apiUrl),
body: {
'username': kullaniciAdiCtrl.text,
'password': sifreCtrl.text,
},
);

if (response.statusCode == 200) {
// Sunucu biletleri (token) gönderdi!
final data = jsonDecode(response.body);
String accessToken = data['access'];

// Bilekliği telefonun hafızasına (çekmeceye) saklıyoruz
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('token', accessToken);

// Başarılı olursa Notlar Ekranına geçiş yap
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const NotesScreen()),
);
} else {
// Şifre veya kullanıcı adı yanlış
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Kullanıcı adı veya şifre hatalı!')),
);
}
} catch (e) {
print("Hata oluştu: $e");
}

setState(() => yukleniyor = false);
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Giriş Yap')),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.lock, size: 80, color: Colors.indigo),
const SizedBox(height: 20),
TextField(
controller: kullaniciAdiCtrl,
decoration: const InputDecoration(labelText: 'Kullanıcı Adı', border: OutlineInputBorder()),
),
const SizedBox(height: 15),
TextField(
controller: sifreCtrl,
obscureText: true,
decoration: const InputDecoration(labelText: 'Şifre', border: OutlineInputBorder()),
),
const SizedBox(height: 20),
yukleniyor
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: girisYap,
style: ElevatedButton.styleFrom(minimumSize: const Size(double.infinity, 50)),
child: const Text('Sisteme Gir (Bileklik Al)'),
),
],
),
),
);
}
}

// -------------------------------------------------------------------
// 2. NOTLAR EKRANI (Sadece Bilekliği Olanların Girebildiği Yer)
// -------------------------------------------------------------------
class NotesScreen extends StatefulWidget {
const NotesScreen({Key? key}) : super(key: key);

@override
_NotesScreenState createState() => _NotesScreenState();
}

class _NotesScreenState extends State<NotesScreen> {
List notlar = [];
bool yukleniyor = true;

final String notlarApiUrl = "http://127.0.0.1:8000/api/notes/";

@override
void initState() {
super.initState();
notlariGetir(); // Ekran açılır açılmaz notları çek
}

Future<void> notlariGetir() async {
// Çekmeceden (hafızadan) bilekliği alıyoruz
SharedPreferences prefs = await SharedPreferences.getInstance();
String? token = prefs.getString('token');

if (token == null) return; // Bileklik yoksa işlem yapma

// Django'ya istek atarken bilekliği Header kısmına ekliyoruz
final response = await http.get(
Uri.parse(notlarApiUrl),
headers: {
'Authorization': 'Bearer $token', // GÜVENLİK KAPISINDAN GEÇİŞ BURADA!
},
);

if (response.statusCode == 200) {
// Veriler başarıyla geldiyse listeye aktar ve ekranı güncelle
setState(() {
notlar = jsonDecode(utf8.decode(response.bodyBytes));
yukleniyor = false;
});
} else {
print("Erişim reddedildi. Token süresi dolmuş olabilir.");
// Burada kullanıcıyı tekrar Login ekranına yönlendirebilirsiniz.
}
}

Future<void> cikisYap() async {
// Çıkış yaparken çekmecedeki bilekliği çöpe atıyoruz
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove('token');
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const LoginScreen()),
);
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Gizli Notlarım'),
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: cikisYap, // Çıkış yap butonu
)
],
),
body: yukleniyor
? const Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: notlar.length,
itemBuilder: (context, index) {
final not = notlar[index];
return Card(
margin: const EdgeInsets.all(10),
child: ListTile(
leading: const Icon(Icons.note),
title: Text(not['title'], style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(not['content']),
),
);
},
),
// Öğrenciler için ek görev: Bu butona basıldığında yeni not ekleme ekranı yapılabilir :)
floatingActionButton: FloatingActionButton(
onPressed: () async {
// Yeni ekrana git ve oradan gelecek sonucu (true/false) bekle
final sonuc = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const AddNoteScreen()),
);

// Eğer sonuc 'true' ise (yani yeni not başarıyla eklendiyse),
// listeyi sunucudan tekrar çekerek güncel haliyle göster!
if (sonuc == true) {
setState(() => yukleniyor = true);
notlariGetir();
}
},
child: const Icon(Icons.add),
),
);
}
}

// -------------------------------------------------------------------
// 3. YENİ NOT EKLEME EKRANI
// -------------------------------------------------------------------
class AddNoteScreen extends StatefulWidget {
const AddNoteScreen({Key? key}) : super(key: key);

@override
_AddNoteScreenState createState() => _AddNoteScreenState();
}

class _AddNoteScreenState extends State<AddNoteScreen> {
final TextEditingController baslikCtrl = TextEditingController();
final TextEditingController icerikCtrl = TextEditingController();
bool kaydediliyor = false;

final String notlarApiUrl = "http://127.0.0.1:8000/api/notes/";

Future<void> notuKaydet() async {
// Alanlar boş mu diye kontrol edelim
if (baslikCtrl.text.isEmpty || icerikCtrl.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Lütfen başlık ve içerik giriniz!')),
);
return;
}

setState(() => kaydediliyor = true);

// Çekmeceden bilekliği alıyoruz
SharedPreferences prefs = await SharedPreferences.getInstance();
String? token = prefs.getString('token');

if (token != null) {
try {
// Django'ya POST isteği atıyoruz (Yeni veri gönderiyoruz)
final response = await http.post(
Uri.parse(notlarApiUrl),
headers: {
'Authorization': 'Bearer $token', // Güvenlik bilekliğimiz
'Content-Type': 'application/json; charset=UTF-8', // Django'ya JSON gönderdiğimizi söylüyoruz
},
body: jsonEncode({
'title': baslikCtrl.text,
'content': icerikCtrl.text,
}),
);

// Django başarıyla oluşturduğunda 201 (Created) kodu döner
if (response.statusCode == 201) {
// Başarılı olduysa önceki ekrana dön ve 'true' (eklendi) bilgisini yolla
Navigator.pop(context, true);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Not kaydedilirken bir hata oluştu.')),
);
}
} catch (e) {
print("Hata: $e");
}
}
setState(() => kaydediliyor = false);
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Yeni Not Ekle')),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
TextField(
controller: baslikCtrl,
decoration: const InputDecoration(
labelText: 'Not Başlığı',
border: OutlineInputBorder()
),
),
const SizedBox(height: 15),
TextField(
controller: icerikCtrl,
maxLines: 5, // İçerik alanı daha geniş olsun
decoration: const InputDecoration(
labelText: 'Not İçeriği',
border: OutlineInputBorder()
),
),
const SizedBox(height: 20),
kaydediliyor
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: notuKaydet,
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 50)
),
child: const Text('Notu Kaydet', style: TextStyle(fontSize: 16)),
),
],
),
),
);
}
}



Öğrencilerinizle Sınıfta Denerken İpuçları:

  • IP Adresi: Eğer Django sunucusunu Pardus bilgisayarınızda (örneğin öğretmen bilgisayarında) çalıştırıyorsanız ve öğrenciler kendi bilgisayarlarındaki Flutter web veya emülatörden bağlanacaksa, Flutter kodundaki 10.0.2.2 yerine laboratuvar ağınızdaki yerel IP adresinizi (Örn: 192.168.1.100) yazmalarını söyleyebilirsiniz.

  • CORS Ayarları: Eğer Flutter'ı Web üzerinden çalıştırarak test edecekseniz, Django tarafında django-cors-headers paketini kurup Flutter'ın istek yapmasına izin vermeyi unutmayın.




Django Kaynak Kod: https://github.com/nuritiras/secret.git

Flutter Kaynak Kod : https://github.com/nuritiras/flutter_secret_notes.git


JWT Nedir?

JWT, kullanıcı doğrulamasını token bazlı yapan bir yöntemdir. Her istekte kullanıcı adı ve şifre gönderilmez, bunun yerine token kullanılır.

JWT yapısı:

header.payload.signature
  • Header → Token türü ve algoritma

  • Payload → Kullanıcı bilgileri (user_id, email vs.)

  • Signature → Token’ın doğruluğunu garantiler

DRF ile JWT Kurulumu

  1. Paketleri yükleyin:

pip install djangorestframework-simplejwt
  1. settings.py yapılandırması:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
}
  1. URL'leri ekleyin:

from django.urls import path
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
  1. Kullanımı:

  • /api/token/POST ile { "username": "user", "password": "pass" }

  • /api/token/refresh/POST ile { "refresh": "token" }

Not: JWT, session bazlı auth yerine kullanıldığında özellikle mobile app ve SPA (React/Flutter) projelerinde avantajlıdır.


2. Authentication (Kimlik Doğrulama)

DRF’de authentication, her istekte kullanıcının kim olduğunu doğrulama işlemi sağlar.

DRF’de Mevcut Authentication Türleri

  1. SessionAuthentication → Django session ile tarayıcı tabanlı.

  2. BasicAuthentication → HTTP Basic Auth (test amaçlı).

  3. TokenAuthentication → DRF’nin default token sistemi.

  4. JWTAuthentication → Modern ve mobil/SPA uyumlu.

Örnek View:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated

class ProfileView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        return Response({
            "user": request.user.username,
            "email": request.user.email
        })

Burada IsAuthenticated sayesinde sadece giriş yapmış kullanıcılar erişebilir.


3. Permissions (İzinler)

Permissions, hangi kullanıcı hangi kaynağa erişebilir sorusunu kontrol eder.

DRF’de hazır permission sınıfları

PermissionAçıklama
AllowAny                    Herkes erişebilir
IsAuthenticated                    Sadece giriş yapmış kullanıcı
IsAdminUser                    Sadece admin kullanıcı
IsAuthenticatedOrReadOnly                    Okuma herkes, yazma giriş yapmış kullanıcı

Custom Permission Örneği

from rest_framework.permissions import BasePermission

class IsOwner(BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj.owner == request.user
  • has_object_permission → Belirli objeler için kontrol sağlar.

  • Kullanımı:

class ArticleDetail(APIView):
    permission_classes = [IsOwner]

    def get_object(self, pk):
        return Article.objects.get(pk=pk)

4. JWT + Custom Claims

JWT payload’ına özel veri ekleyebilirsiniz:

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        token['username'] = user.username
        token['is_admin'] = user.is_staff
        return token

urls.py:

from .views import MyTokenObtainPairView

path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),

5. JWT + Refresh Token Mantığı

  • Access Token: kısa ömürlü (5–15 dk)

  • Refresh Token: uzun ömürlü (7–30 gün), access token yenilemek için kullanılır.

Kullanıcı, refresh token ile her zaman yeni access token alabilir.


6. Örnek İleri Seviye View

from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from .models import Article
from .serializers import ArticleSerializer

class ArticleListCreateView(generics.ListCreateAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [IsAuthenticated]

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)
  • Burada sadece giriş yapmış kullanıcı yeni içerik ekleyebilir.

  • perform_create ile otomatik owner ataması yapılır.


7. Güvenlik İpuçları

  1. JWT Secret Key’i güçlü seçin: settings.pySECRET_KEY

  2. HTTPS kullanın: Token’lar şifrelenmeden gönderilmemeli

  3. Short-lived Access Token + Long-lived Refresh Token

  4. Blacklist özelliğini aktif edin (rest_framework_simplejwt.token_blacklist)


8. Profesyonel Diyagram

[Client] <--JWT--> [DRF API]
   |                   |
   |--Login------------>|
   |<--Access/Refresh-- |
   |--API Request------>|
   |--JWT Token Header->|
   |<--Data------------ |
  • Client → React, Flutter veya Postman olabilir

  • DRF → JWT ile auth kontrolü

  • Token, header Authorization: Bearer <token> şeklinde gönderilir.




1️⃣ Proje Yapısı

school_api/              # Django projesi
├─ manage.py
├─ school_api/           # Settings
│  ├─ settings.py
│  ├─ urls.py
│  └─ wsgi.py
├─ users/                # Kullanıcı uygulaması
│  ├─ models.py
│  ├─ serializers.py
│  ├─ views.py
│  ├─ urls.py
│  └─ permissions.py
└─ articles/             # Örnek içerik uygulaması
   ├─ models.py
   ├─ serializers.py
   ├─ views.py
   ├─ urls.py
   └─ permissions.py

2️⃣ settings.py – DRF ve JWT

INSTALLED_APPS = [
    'rest_framework',
    'rest_framework_simplejwt.token_blacklist',
    'users',
    'articles',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
}

3️⃣ Kullanıcı Modeli (users/models.py)

from django.contrib.auth.models import AbstractUser
from django.db import models

class CustomUser(AbstractUser):
    bio = models.TextField(blank=True, null=True)
    
    def __str__(self):
        return self.username

AUTH_USER_MODEL = 'users.CustomUser' ayarını settings.py’ye eklemeyi unutmayın.


4️⃣ Kullanıcı Serializer (users/serializers.py)

from rest_framework import serializers
from .models import CustomUser
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomUser
        fields = ('id', 'username', 'email', 'bio')

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        token['username'] = user.username
        return token

5️⃣ Kullanıcı Views (users/views.py)

from rest_framework import generics, permissions
from .models import CustomUser
from .serializers import UserSerializer, MyTokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView

# JWT login view
class MyTokenObtainPairView(TokenObtainPairView):
    serializer_class = MyTokenObtainPairSerializer

# Register view
class RegisterView(generics.CreateAPIView):
    queryset = CustomUser.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.AllowAny]

# Profile view
class ProfileView(generics.RetrieveAPIView):
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_object(self):
        return self.request.user

6️⃣ Custom Permission (articles/permissions.py)

from rest_framework.permissions import BasePermission

class IsOwner(BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj.owner == request.user

7️⃣ Article Model & Serializer (articles/models.py & serializers.py)

# models.py
from django.db import models
from users.models import CustomUser

class Article(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    owner = models.ForeignKey(CustomUser, related_name='articles', on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    
    def __str__(self):
        return self.title

# serializers.py
from rest_framework import serializers
from .models import Article

class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = '__all__'
        read_only_fields = ('owner',)

8️⃣ Article Views (articles/views.py)

from rest_framework import generics
from .models import Article
from .serializers import ArticleSerializer
from .permissions import IsOwner
from rest_framework.permissions import IsAuthenticated

class ArticleListCreateView(generics.ListCreateAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [IsAuthenticated]

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [IsOwner]

9️⃣ URL Yapısı

# school_api/urls.py
from django.urls import path, include

urlpatterns = [
    path('api/users/', include('users.urls')),
    path('api/articles/', include('articles.urls')),
]

# users/urls.py
from django.urls import path
from .views import MyTokenObtainPairView, RegisterView, ProfileView

urlpatterns = [
    path('login/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('register/', RegisterView.as_view(), name='register'),
    path('profile/', ProfileView.as_view(), name='profile'),
]

# articles/urls.py
from django.urls import path
from .views import ArticleListCreateView, ArticleDetailView

urlpatterns = [
    path('', ArticleListCreateView.as_view(), name='article_list_create'),
    path('<int:pk>/', ArticleDetailView.as_view(), name='article_detail'),
]

🔹 ER Diyagramı

[CustomUser]
  |--id
  |--username
  |--email
  |--bio
      |
      | 1
      | owns
      | *
[Article]
  |--id
  |--title
  |--content
  |--owner_id (FK CustomUser)
  |--created_at
  • CustomUserArticle arasında bire çok ilişki.

  • JWT Authentication → tüm endpointlerde token kontrolü.

  • IsOwner → sadece sahip kullanıcı CRUD yapabilir.


💡 Bu yapı ile:

  • Kullanıcı kayıt, login ve profile görüntüleyebilir

  • JWT ile güvenli auth yapılır

  • Kullanıcı sadece kendi oluşturduğu içerikleri değiştirebilir

  • Admin veya diğer permission kuralları kolayca eklenebilir




🔹 Flutter + Django REST API: 10 Proje Fikri

  1. Okul Yönetim Sistemi

    • Öğrenci, öğretmen, ders, sınav ve not yönetimi.

    • Kullanıcı tipine göre rol tabanlı yetkilendirme (Öğrenci / Öğretmen / Admin).

    • API: Öğrenci kayıt, not ekleme, ders listesi.

  2. Sağlıklı Yaşam Takip Uygulaması

    • Adım sayacı, su tüketimi, kalori takibi.

    • Django API: Kullanıcı verileri CRUD, grafik verileri JSON olarak döndürme.

  3. Kütüphane Yönetim Sistemi

    • Kitap ödünç alma/iade, stok yönetimi, kullanıcı bildirimleri.

    • API: Kitap listesi, ödünç alınan kitap, iade işlemleri.

  4. E-Ticaret Mobil Uygulaması

    • Ürün listeleme, sepete ekleme, ödeme entegrasyonu.

    • API: Ürün CRUD, sipariş oluşturma, ödeme durum takibi.

  5. Etkinlik Takip ve Rezervasyon Uygulaması

    • Kullanıcı etkinlik oluşturabilir ve rezervasyon yapabilir.

    • API: Etkinlik CRUD, katılım doğrulama.

  6. Sosyal Medya Mini Uygulaması

    • Gönderi paylaşımı, yorum ve beğeni sistemi.

    • API: Kullanıcı profili, gönderi listesi, yorum ekleme.

  7. Haber / Blog Uygulaması

    • Kategoriye göre haber / blog listeleme.

    • API: Haber CRUD, kategori filtreleme, detaylı haber görüntüleme.

  8. Görev ve Proje Yönetim Uygulaması

    • Kullanıcı görev ekleyebilir, projelere atanabilir, durum güncelleyebilir.

    • API: Görev CRUD, proje bazlı filtreleme.

  9. Online Anket / Quiz Uygulaması

    • Anket oluşturma, cevaplama ve sonuçları görüntüleme.

    • API: Anket CRUD, kullanıcı cevap takibi, sonuç istatistikleri.

  10. Finans Takip / Bütçe Yönetimi

    • Gelir-gider takibi, kategori bazlı analiz.

    • API: Kullanıcı finans CRUD, aylık raporlar.


🔹 Flutter için Profesyonel API Mimarisi (Clean Architecture)

Clean Architecture, kodu katmanlara ayırarak yönetir ve test edilebilirliği artırır. Flutter + REST API için tipik yapı:

lib/
│
├─ data/               # Veri katmanı
│   ├─ models/         # JSON model sınıfları
│   ├─ repositories/   # API çağrıları ve veri çekme
│   └─ datasources/    # Remote (Django REST) ve local (SQLite, Hive) kaynaklar
│
├─ domain/             # İş mantığı
│   ├─ entities/       # Core entity sınıfları
│   └─ usecases/       # İş kuralları (veri işleme, filtreleme)
│
├─ presentation/       # UI katmanı
│   ├─ blocs/ / cubits/ # State management (Bloc, Riverpod, Provider)
│   ├─ pages/          # Ekranlar
│   └─ widgets/        # UI bileşenleri
│
└─ core/               # Global yardımcılar
    ├─ network/        # HTTP client, interceptors
    ├─ utils/          # Helper fonksiyonlar
    └─ constants/      # Sabitler (URL, Keys)

🔹 Örnek API çağrısı (Repository Katmanı)

class UserRepository {
  final ApiClient apiClient;

  UserRepository(this.apiClient);

  Future<User> getUser(int id) async {
    final response = await apiClient.get('/users/$id/');
    return User.fromJson(response.data);
  }
}

🔹 Bloc / Cubit ile state yönetimi (Presentation)

class UserCubit extends Cubit<UserState> {
  final UserRepository repository;
  UserCubit(this.repository) : super(UserInitial());

  Future<void> fetchUser(int id) async {
    emit(UserLoading());
    try {
      final user = await repository.getUser(id);
      emit(UserLoaded(user));
    } catch (e) {
      emit(UserError('Kullanıcı yüklenemedi'));
    }
  }
}

🔹 Avantajları

  • Test edilebilir: Domain ve Data katmanları bağımsız test edilebilir.

  • Esnek: API değişse bile UI ve iş mantığı minimal etkilenir.

  • Modüler: Farklı projelerde katmanları tekrar kullanabilirsin.



Yorumlar

Bu blogdaki popüler yayınlar

Pardus Üzerine Django Kurulumu

Python ile Web Geliştirme: Django App Oluşturma