Ön Hazırlık ve Gereksinimler
Dinamik yetkilendirme sistemi kurmak için projenizde güncel Flutter SDK (3.24 ve üzeri) ve state yönetimi için "Riverpod" veya "Bloc" gibi reaktif bir kütüphane kullanmanızı öneririm. Bu rehberde, temiz kod prensiplerine uygun olması adına Riverpod tercih edilmiştir.
- Flutter SDK: 3.24.0 veya daha yeni bir sürüm.
- State Yönetimi: Riverpod (Provider paketi).
- Güvenlik: Secure Storage (hassas verilerin şifrelenmiş saklanması için).
- API İletişimi: Dio (Interceptor desteği ile yetkilendirme başlıklarını yönetmek için).
Model Katmanını Oluşturma
Yetkilendirme mekanizmasının temeli, kullanıcı verisinin ve rollerinin doğru modellenmesidir. Sunucudan gelen JSON verisini karşılayacak bir model sınıfı oluşturarak işe başlıyoruz. Rolleri bir liste veya enum olarak tutmak, kodun okunabilirliğini artırır.
class UserProfile {
final String id;
final String username;
final List roles; // Örn: ['admin', 'editor', 'user']
UserProfile({required this.id, required this.username, required this.roles});
factory UserProfile.fromJson(Map json) {
return UserProfile(
id: json['id'],
username: json['username'],
roles: List.from(json['roles']),
);
}
}
Yukarıdaki model, sunucudan gelen kullanıcı bilgilerini Dart nesnesine dönüştürür. roles alanı, kullanıcının hangi yetkilere sahip olduğunu tutan anahtar listesidir.
Riverpod ile Yetki Durumunu Yönetme
Uygulama genelinde yetkiyi yönetmek için bir Notifier sınıfı kullanacağız. Bu sınıf, kullanıcı giriş yaptığında yetkilerini belleğe yükler ve uygulama genelinde erişilebilir kılar.
class AuthNotifier extends Notifier {
@override
UserProfile? build() => null;
void login(UserProfile user) {
state = user;
}
void logout() {
state = null;
}
bool hasPermission(String requiredRole) {
return state?.roles.contains(requiredRole) ?? false;
}
}
final authProvider = NotifierProvider(AuthNotifier.new);
Bu yapı sayesinde, uygulamanın herhangi bir yerinde ref.read(authProvider.notifier).hasPermission('admin') diyerek kullanıcının o sayfayı görmeye yetkili olup olmadığını anlık olarak kontrol edebilirsiniz.
Yetkilendirme Kontrol Tablosu
Farklı kullanıcı rollerinin erişim seviyelerini aşağıdaki tablo üzerinden planlayabilirsiniz. Bu yapı, geliştirme sürecinde hata yapma payınızı minimize eder.
| Rol | Erişim Seviyesi | Özellikler |
|---|---|---|
| Admin | Tam Yetki | Ayarlar, Kullanıcı Yönetimi, Veri Analizi |
| Editor | Kısmi Yetki | İçerik Düzenleme, Yorum Yönetimi |
| User | Sınırlı Yetki | Profil Görüntüleme, İçerik Okuma |
Ekran Bazlı Dinamik Yetkilendirme
Bir ekranın sadece belirli rollere açık olması durumunda, Widget seviyesinde bir koruyucu (Guard) mekanizması kullanmak en temiz yöntemdir. Aşağıdaki kod bloğu, belirli bir role sahip olmayan kullanıcıları otomatik olarak ana sayfaya yönlendirir.
class ProtectedWidget extends ConsumerWidget {
final String requiredRole;
final Widget child;
const ProtectedWidget({required this.requiredRole, required this.child});
@override
Widget build(BuildContext context, WidgetRef ref) {
final hasAccess = ref.read(authProvider.notifier).hasPermission(requiredRole);
if (!hasAccess) {
return const Scaffold(body: Center(child: Text("Erişim reddedildi!")));
}
return child;
}
}
Bu ProtectedWidget yapısını, herhangi bir sayfanın en üstünde kullanarak o sayfayı sadece "admin" rolüne sahip kullanıcıların görmesini sağlayabilirsiniz.
Kritik Güvenlik Uyarısı: İstemci tarafındaki (Flutter) yetkilendirme kontrolleri sadece kullanıcı deneyimini iyileştirmek içindir. Asıl güvenlik her zaman sunucu (backend) tarafında yapılmalıdır. İstemci tarafındaki kontrolleri bypass etmek kolaydır; bu nedenle API uç noktalarınızda mutlaka JWT (JSON Web Token) veya benzeri bir yöntemle sunucu tarafında yetki doğrulaması yapın.
API İsteklerinde Yetki Yönetimi
Dio gibi kütüphanelerle çalışırken, her isteğe yetki bilgisini otomatik eklemek için Interceptor kullanın. Bu, token'ın güvenli bir şekilde taşınmasını sağlar.
final dio = Dio();
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
final token = secureStorage.read(key: 'jwt');
options.headers['Authorization'] = 'Bearer $token';
return handler.next(options);
},
));
Bu yapı, uygulamanızın her ağ isteğinde yetkilendirme başlığını (header) otomatik olarak ekleyerek, sunucunun kullanıcının kim olduğunu ve yetkilerini doğrulamasını sağlar.
Sıkça Sorulan Sorular
Flutter'da yetkilendirme neden hem client hem server tarafında yapılmalı?
Client tarafındaki kontroller sadece arayüzü gizlemek içindir. Kötü niyetli kullanıcılar uygulama kodunu manipüle edebilir. Server tarafı, verinin gerçek koruyucusudur.
JWT token'ı güvenli bir şekilde nerede saklamalıyım?
Flutter için flutter_secure_storage paketini kullanın. Bu paket, Android'de Keystore ve iOS'ta Keychain kullanarak verileri şifreli saklar.
Kullanıcının rolü değişirse uygulama bunu nasıl anlar?
Uygulama açılışında veya kritik aksiyonlardan sonra kullanıcı profilini tekrar çeken bir "refresh" mekanizması kurmalı ve Riverpod state'ini güncellemelisiniz.
Dinamik yetkilendirme performans kaybına yol açar mı?
Hayır, doğru yapılandırılmış bir state yönetimi (Riverpod gibi) ile yetki kontrolleri milisaniyeler içinde gerçekleşir ve kullanıcı deneyimini etkilemez.
Roller için enum mu yoksa string mi kullanmalıyım?
Küçük projelerde enum yeterlidir. Ancak dinamik, veritabanından gelen ve sürekli değişen roller için string listesi kullanmak daha esnektir.
İleri Seviye Hata Ayıklama ve İzleme Stratejileri
Dinamik yetkilendirme mekanizmalarında karşılaşılan en büyük zorluk, kullanıcıların neden belirli bir ekrana erişemediğini veya neden yetkisiz işlem hatası aldığını tespit etmektir. Uygulama büyüdükçe, "Neden bu kullanıcı bu butonu görmüyor?" sorusuna yanıt vermek için merkezi bir loglama ve izleme yapısı kurmanız gerekir.
Yetkilendirme Hatalarını İzleme
Yetkilendirme katmanında bir hata oluştuğunda, sadece kullanıcıya "Yetkiniz yok" mesajı göstermek yerine, durumu geliştirici konsolunda veya bir hata izleme servisinde (Sentry, Firebase Crashlytics gibi) detaylandırmalısınız.
void checkPermission(String requiredPermission) {
final userPermissions = ref.read(authProvider).permissions;
if (!userPermissions.contains(requiredPermission)) {
// Hata izleme servisine log gönder
Logger.logError(
'Yetki Hatası: Kullanıcı {${ref.read(authProvider).userId}} '
'gerekli olan {$requiredPermission} yetkisine sahip değil.'
);
throw AuthorizationException('Bu işlem için gerekli yetkiniz bulunmuyor.');
}
}
State İzleme (State Inspector)
Riverpod kullanırken, yetkilendirme durumunun (state) uygulama yaşam döngüsü boyunca nasıl değiştiğini görmek için ProviderObserver sınıfını kullanabilirsiniz. Bu, özellikle kullanıcı rolü anlık olarak değiştiğinde (örneğin admin panelinden bir yetki kısıtlaması yapıldığında) uygulamanın nasıl tepki verdiğini canlı olarak izlemenizi sağlar.
Alternatif Yaklaşım: Yetkilendirme İçin Mixin Kullanımı
Kod tekrarını azaltmak ve yetkilendirme mantığını widget ağacından bağımsız hale getirmek için Mixin yapısını kullanmak oldukça temiz bir çözümdür. Bu yöntemle, yetki kontrolünü doğrudan build metodunun içine gömmek yerine, ilgili sayfaya bir "yetki kalkanı" takabilirsiniz.
Aşağıdaki örnekte, belirli bir yetkiye sahip olmayan kullanıcıları otomatik olarak yönlendiren bir PermissionGuardMixin yapısını inceleyebilirsiniz:
mixin PermissionGuardMixin on ConsumerStatefulWidget {
String get requiredPermission;
void checkAccess(WidgetRef ref, BuildContext context) {
final hasAccess = ref.read(authProvider).hasPermission(requiredPermission);
if (!hasAccess) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pushReplacementNamed('/access-denied');
});
}
}
}
class AdminDashboard extends ConsumerStatefulWidget with PermissionGuardMixin {
@override
String get requiredPermission => 'admin_access';
@override
ConsumerState createState() => _AdminDashboardState();
}
class _AdminDashboardState extends ConsumerState {
@override
void initState() {
super.initState();
widget.checkAccess(ref, context);
}
@override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text("Admin Paneli")));
}
}
İpucu: Mixin kullanımı, karmaşık widget ağaçlarındaif-elseblokları oluşturmaktan çok daha okunabilirdir. Ayrıca, bu yapıyı birBasePagesınıfı oluşturarak tüm sayfalarınıza kalıtım yoluyla da entegre edebilirsiniz.
Bu yöntemle, uygulamanızın yetkilendirme mantığını merkezi bir noktadan yönetebilir ve her yeni sayfa için sadece gerekli olan requiredPermission değerini tanımlayarak güvenliği standart hale getirebilirsiniz.
Sonuç
Flutter & mobil ile dinamik kullanıcı yetkilendirme mekanizması, uygulamanızın profesyonel ve güvenli bir yapıya kavuşması için temel taşıdır. Bu rehberde; modelleme, state yönetimi, widget koruması ve API güvenliği üzerine adım adım bir yol haritası çizdik. Unutmayın, güvenlik bir süreçtir; uygulamanızı güncel tutmak ve sunucu tarafındaki yetkilendirme kontrollerini asla ihmal etmemek en önemli kuraldır.
Bir sonraki adım olarak, yetkilendirme mekanizmanıza "Rol Tabanlı Erişim Kontrolü" (RBAC) yerine "Öznitelik Tabanlı Erişim Kontrolü" (ABAC) ekleyerek, kullanıcıların sadece kendi oluşturdukları verileri düzenleyebileceği daha gelişmiş bir sistem tasarlayabilirsiniz.
Sorumluluk Reddi: Bu makalede paylaşılan kod örnekleri eğitim amaçlıdır. Üretim ortamında (production) kullanmadan önce mutlaka güvenlik testlerinden geçirin, şifreleme yöntemlerini güncel tutun ve yetkilendirme mantığınızı sunucu tarafındaki API güvenlik standartlarıyla (OAuth2, OpenID Connect vb.) uyumlu hale getirin.


Yorumlar (0)
Yorum Yaz