October 24, 2026
Authentication & Authorization Eğitim Serisi — Bölüm 16: ABAC — Attribute-Based Access Control
Modül V — Authorization Modelleri

By Sahin Yelkenci
16 min read
"RBAC, 'rolün ne?' der; bilir ve eyler. ABAC, 'sen kimsin, kaynak ne, zaman ne, koşullar ne?' der; düşünür ve karar verir. Birincisinin gücü hızında, ikincisininki derinliğindedir."
» İçerikler
» 16.1. Açılış — Hastane Senaryosunun Derinleşmesi » 16.2. ABAC Felsefesi — "Rol Değil, Koşullar" » 16.3. Dört Attribute Kategorisi » 16.4. ABAC Policy — Boolean Expression Anatomisi » 16.5. Tam Bir ABAC Evaluation Akışı » 16.6. RBAC + ABAC = Hibrit Pattern (Modern Best Practice) » 16.7. Pratik — Spring Security ile ABAC » 16.8. ABAC'in Modern Policy Engine'ları » 16.9. ABAC vs RBAC — Trade-offs » 16.10. Anti-Patterns — ABAC Tasarımında Yaşayan Yanılgılar » 16.11. Üç Gözlem Sorusu » 16.12. Kapanış ve Bir Sonraki Adıma Köprü
16.1. Açılış — Hastane Senaryosunun Derinleşmesi
Bölüm 15'te kullandığımız hastane analojisini hatırlayın. Doktor, hemşire, müdür, temizlik, güvenlik — her rol bir kapı açıyordu. RBAC ile "doktor mu?" sorusuna cevap veriyorduk. Şimdi senaryoyu bir derinlik daha açalım, çünkü gerçek hastanelerde sorular çok daha sofistikedir.
Doktor X, gece nöbetindeki bir Cardiology doktoru. Acil servise 23:47'de bir hasta geldi — Y hastası, 67 yaşında, kalp krizi şüphesi. X'in Y'nin tüm tıbbi geçmişine erişmesi gerekiyor. Ama X aynı zamanda Cardiology dışındaki başka hastaların dosyalarını da görebilir mi? Pediatri'deki bir çocuğun dosyasını okuyabilir mi? Bir başka şehirdeki şubedeki bir hastanın bilgilerini? Aile sağlık geçmişini de görebilir mi, yoksa sadece kardiyolojik durumla mı sınırlı? Hastaneye dışarıdan bağlantı yaparken (örneğin evden, telekonsültasyon) bu erişim aynı mı, yoksa farklı kurallar mı çalışıyor?
Bu soruların hiçbiri "doktor mu?" sorusunun türevleri değil. Hepsi farklı attribute'lar üzerinden cevaplanır:
- Doktorun departmanı (Cardiology)
- Şu anki vardiyası (gece nöbeti)
- Hastanın tedavi gördüğü departman (Cardiology eşleşmesi)
- Hastanın bağlı olduğu doktor (assigned physician'la match)
- Erişim zamanı (iş saatleri / acil saatler)
- Erişim yeri (hastane içi network / dışarıdan VPN)
- Erişilen veri kategorisi (kardiyolojik / genel / hassas geçmiş)
Tüm bu attribute'ların kombinasyonu, gerçek dünya authorization'ının dayandığı şeydir. RBAC bu sofistikasyonu doğal olarak ifade edemez. "Cardiology-gece-doktoru-aktif-hasta-için-trusted-network" rolü yaratırsanız, role explosion'ın matematiksel kesinliğine düşersiniz; Bölüm 15'teki Hikâye 2'nin alt-küme bir versiyonu. Çıkış, role'ün ötesine geçmek; bireysel attribute'larla policy'ler tanımlamak.
Bu yaklaşımın adı ABAC — Attribute-Based Access Control. Bu bölümde, ABAC'ın kavramsal omurgasını, dört attribute kategorisini (subject, resource, action, environment), policy expression'larını, modern policy engine'leri (AWS Cedar, AWS IAM), Spring Security ile pratik implementasyonu, ve RBAC ile birlikte nasıl hibrit kullanılacağını işleyeceğiz. Bölüm sonunda, modern authorization'ın "RBAC sade ama yetmez, ABAC esnek ama karmaşık" gerilimi karşısında nasıl orta yol bulduğunu göreceğiz.
16.2. ABAC Felsefesi — "Rol Değil, Koşullar"
ABAC'ın özünde yatan fikir tek bir cümleyle ifade edilir:
"Authorization kararı, kullanıcının rolüne değil; istek anındaki tüm ilgili attribute'ların değerlendirilmesine dayanır."
Bu, RBAC'a göre paradigmatik bir farktır. RBAC "kullanıcının rolü ne, role'ün izni ne" diye sorar — discrete, scalar bir karar. ABAC "her boyutun değeri ne, bu kombinasyon authorization için yeterli mi" diye sorar — multi-dimensional, programmable bir karar.
Bunun pratikteki anlamı şudur: ABAC bir policy language ile çalışır. Yani authorization mantığı, kodun içinde gömülü değil; bir policy dokümanında yazılır. Bu doküman, bir engine tarafından runtime'da değerlendirilir ve allow/deny kararına dönüşür. Klasik bir ABAC policy şöyle görünür (pseudo-syntax):
permit
subject.role == "doctor"
AND subject.department == resource.department
AND resource.assigned_physician == subject.user_id
AND environment.time BETWEEN 06:00 AND 22:00
AND environment.network_zone == "hospital_internal"permit
subject.role == "doctor"
AND subject.department == resource.department
AND resource.assigned_physician == subject.user_id
AND environment.time BETWEEN 06:00 AND 22:00
AND environment.network_zone == "hospital_internal"Bu cümle, RBAC'ta tek bir role olarak ifade edilemez. Aynı zamanda yüzlerce custom permission ile de ifade edilemez (kombinatorik patlama). ABAC'ın gücü, bağımsız boyutları kompoze edebilmesinde yatıyor.
ABAC'ın ortaya çıkış yolu da bunu yansıtır. NIST SP 800–162 dokümanı 2014'te ABAC'ı formal olarak tanımladı. Daha önce XACML (eXtensible Access Control Markup Language, OASIS standardı) IBM tarafından geliştirilmişti — ama XACML XML-ağır syntax'ı sebebiyle yaygınlaşamadı. Esas yaygınlaşma cloud çağında geldi: AWS IAM Policies ABAC mantığını mainstream'e taşıdı. 2023'te AWS, Cedar denilen modern policy language'i open source olarak yayınladı; bu, ABAC'ın yeni nesil sözcüsü oldu.
16.3. Dört Attribute Kategorisi
ABAC'ın yapısal omurgası, dört attribute kategorisidir. Her authorization kararı bu dört kategorinin attribute'larının kombinasyonu üzerine yapılır. Her kategoriyi detaylandıralım.
16.3.1. Subject Attributes — Kullanıcının Profili
Subject = authentication ile kimliği doğrulanmış kullanıcı. Subject'in attribute'ları, kullanıcının her authorization kararında ifade edilebilen tüm özellikleridir:
- user.id — opaque user identifier (UUID gibi)
- user.email — e-posta (PII, dikkat)
- user.department — kullanıcının çalıştığı departman
- user.clearance — güvenlik clearance seviyesi (public, confidential, secret, top-secret)
- user.role — RBAC role'ü ile uyumlu attribute (ABAC + RBAC hibrid için)
- user.tenure — kullanıcının ne kadar süredir aktif olduğu (yeni kullanıcılara daha kısıtlı izin)
- user.location — kullanıcının kayıtlı çalışma yeri
- user.tenant_id — multi-tenant scope
- user.mfa_enrolled — MFA aktif mi (Modül IV ile bağlantı)
Subject attribute'ları, Identity Provider (Authorization Server) tarafından sağlanır. OIDC ID Token'ında bu attribute'ların pek çoğu zaten taşınıyor (Bölüm 11'den hatırlayın); ABAC engine, ID Token'dan ya da ek bir kullanıcı profile servisinden okur.
16.3.2. Resource Attributes — Kaynağın Profili
Resource = erişilmek istenen veri/işlem nesnesi. Bir order, bir document, bir API endpoint, bir database record. Resource'un attribute'ları:
- resource.id — unique identifier
- resource.owner — kaynağın sahibi (kullanıcı/department)
- resource.department — kaynağın bağlı olduğu departman
- resource.classification — sensitivity classification (public, internal, confidential, restricted)
- resource.region — kaynağın geographically nerede saklandığı (GDPR/KVKK için kritik)
- resource.tags — flexible metadata (project tag, compliance tag, sensitive flag)
- resource.created_at, updated_at — temporal metadata
- resource.state — resource'un durumu (active, archived, deleted, frozen)
Resource attribute'ları, uygulama veritabanından ya da bir metadata service'tan gelir. ABAC engine'in karar verirken bu attribute'lara ulaşması gerek — bu bir PIP (Policy Information Point, Bölüm 15'ten hatırlayın) sorusudur.
16.3.3. Action Attributes — İşlemin Profili
Action = kullanıcının yapmak istediği işlem. Üç önemli attribute:
- action.type — yüksek seviyeli aksiyon tipi (read, write, delete, share, export)
- action.method — protokol seviyesi (HTTP method: GET, POST, PUT, DELETE)
- action.sensitivity — aksiyonun hassasiyet seviyesi (low, normal, high, critical)
Action attribute'ları en az kategoridir ama önemlidir. Örneğin, "read" allowed olabilir, "export" reddedilebilir — ikisi de aynı resource için, aynı kullanıcı için. Aksiyon-attribute'u bu ayrımı yapar.
16.3.4. Environment Attributes — Context
Environment = isteğin yapıldığı bağlam. ABAC'ın en zengin kategorisi; çünkü context-awareness ABAC'ın diğer modellerden ayrıldığı yer.
- request.time — istek zamanı (iş saatleri kontrolü)
- request.ip — request IP'si
- request.geo — kullanıcının coğrafyası (IP'den çıkarılır)
- request.device_trust — cihaz trust seviyesi (managed device, BYOD, unknown)
- request.device_id — specific device tanımlayıcısı
- request.network_zone — istek geldiği network (corporate VPN, public internet, branch office)
- request.mfa_verified — son MFA verification ne zaman yapıldı (taze authentication)
- request.session_age — session ne kadar eskiyim
- request.user_agent — browser/client tipi
Environment attribute'ları, çoğu zaman runtime'da hesaplanır — IP'den geo lookup, current time, last MFA timestamp gibi. Bunlar dynamic attribute'lardır; subject ve resource'tan farklı olarak request-by-request değişebilir.
16.4. ABAC Policy — Boolean Expression Anatomisi
ABAC'ın kalbi, attribute'lar üzerinde tanımlanan policy expression'larıdır. Bir policy, "şu koşullar sağlanırsa allow ver" formatında bir boolean ifadesidir.
Klasik bir ABAC policy yapısı:
EFFECT [Permit | Deny]
TARGET (hangi resource/action için bu policy çalışsın)
CONDITION (attribute'ların boolean kombinasyonu)EFFECT [Permit | Deny]
TARGET (hangi resource/action için bu policy çalışsın)
CONDITION (attribute'ların boolean kombinasyonu)Effect: Policy'nin sonucu — Permit (allow) ya da Deny. Çoğu engine "deny varsa daima allow'u geçer" prensibiyle çalışır.
Target: Policy hangi durumlarda çalışır? Genelde resource tipi + action tipi ile. Örneğin: "tüm medical_record resource'ları için, read aksiyonu yapılırken bu policy çalışsın."
Condition: Asıl boolean ifade. Subject, resource, action, environment attribute'larının kombinasyonu.
Bir gerçek dünya senaryosu — hastane medical records:
POLICY: doctor_can_read_assigned_patient_records
EFFECT: Permit
TARGET: action.type == "read" AND resource.type == "medical_record"
CONDITION:
subject.role == "doctor"
AND subject.department == resource.department
AND (
resource.assigned_physician == subject.user_id
OR resource.consultation_doctors CONTAINS subject.user_id
)
AND environment.network_zone IN ["hospital_internal", "trusted_vpn"]
AND environment.mfa_verified_within_minutes <= 120POLICY: doctor_can_read_assigned_patient_records
EFFECT: Permit
TARGET: action.type == "read" AND resource.type == "medical_record"
CONDITION:
subject.role == "doctor"
AND subject.department == resource.department
AND (
resource.assigned_physician == subject.user_id
OR resource.consultation_doctors CONTAINS subject.user_id
)
AND environment.network_zone IN ["hospital_internal", "trusted_vpn"]
AND environment.mfa_verified_within_minutes <= 120Bu policy, üst düzey doktor erişim mantığını ifade ediyor. RBAC'ta bu mantığı tek role ile karşılamaya kalksaydık:
- "DoctorReadingOwnPatient" rolü mü?
- Bu role hangi permission'larla?
- Network zone kontrolü nereye? MFA freshness kontrolü?
Cevap: RBAC ile doğal değil — custom code yazmadan yapılamaz. ABAC, bu mantığı declarative ve policy-as-code olarak ifade eder.
ABAC policy language'leri sektörde birkaç tanedir. Akademik olarak XACML (XML bazlı, OASIS standardı), AWS dünyasında AWS IAM Policies (JSON bazlı), modern olarak AWS Cedar (custom DSL). Aşağıda her birinin syntax'ını karşılaştıralım.
16.4.1. XACML Syntax (Akademik, XML-ağır)
<Policy PolicyId="doctor-read-record" RuleCombiningAlgId="...permit-overrides">
<Target>
<AnyOf>
<AllOf>
<Match MatchId="...string-equal">
<AttributeValue DataType="string">medical_record</AttributeValue>
<AttributeDesignator Category="resource" AttributeId="resource-type"/>
</Match>
<Match MatchId="...string-equal">
<AttributeValue DataType="string">read</AttributeValue>
<AttributeDesignator Category="action" AttributeId="action-type"/>
</Match>
</AllOf>
</AnyOf>
</Target>
<Rule RuleId="cardiology-doctor-rule" Effect="Permit">
<Condition>
<Apply FunctionId="...string-equal">
<AttributeDesignator Category="subject" AttributeId="department"/>
<AttributeDesignator Category="resource" AttributeId="department"/>
</Apply>
</Condition>
</Rule>
</Policy><Policy PolicyId="doctor-read-record" RuleCombiningAlgId="...permit-overrides">
<Target>
<AnyOf>
<AllOf>
<Match MatchId="...string-equal">
<AttributeValue DataType="string">medical_record</AttributeValue>
<AttributeDesignator Category="resource" AttributeId="resource-type"/>
</Match>
<Match MatchId="...string-equal">
<AttributeValue DataType="string">read</AttributeValue>
<AttributeDesignator Category="action" AttributeId="action-type"/>
</Match>
</AllOf>
</AnyOf>
</Target>
<Rule RuleId="cardiology-doctor-rule" Effect="Permit">
<Condition>
<Apply FunctionId="...string-equal">
<AttributeDesignator Category="subject" AttributeId="department"/>
<AttributeDesignator Category="resource" AttributeId="department"/>
</Apply>
</Condition>
</Rule>
</Policy>
XACML'in kompleksitesi açık. Tek bir basit koşul için bu kadar XML — yaygınlaşmasının önündeki ana engel.
16.4.2. AWS IAM Policy (JSON, Yaygın)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["medical-records:Read"],
"Resource": "arn:aws:records:*:*:patient/*",
"Condition": {
"StringEquals": {
"aws:PrincipalTag/Role": "Doctor",
"aws:PrincipalTag/Department": "${aws:ResourceTag/Department}"
},
"IpAddress": {
"aws:SourceIp": ["10.0.0.0/16", "192.168.100.0/24"]
},
"DateGreaterThan": {
"aws:CurrentTime": "${aws:MultiFactorAuthAge}"
}
}
}
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["medical-records:Read"],
"Resource": "arn:aws:records:*:*:patient/*",
"Condition": {
"StringEquals": {
"aws:PrincipalTag/Role": "Doctor",
"aws:PrincipalTag/Department": "${aws:ResourceTag/Department}"
},
"IpAddress": {
"aws:SourceIp": ["10.0.0.0/16", "192.168.100.0/24"]
},
"DateGreaterThan": {
"aws:CurrentTime": "${aws:MultiFactorAuthAge}"
}
}
}
]
}
AWS IAM Policies, JSON tabanlı ABAC örneğidir.
${aws:PrincipalTag/...}ve${aws:ResourceTag/...}ile tag bazlı attribute matching yapılır. Bu, "kullanıcının tag'i ile resource'un tag'i eşleşmeli" denmesini sağlar — yaygın bir ABAC pattern'i.
16.4.3. AWS Cedar (Modern, DSL)
2023'te AWS, Cedar'ı open source olarak duyurdu. ABAC için purpose-built bir dil:
permit (
principal in Group::"Doctors",
action in [Action::"ReadRecord", Action::"WriteNote"],
resource is MedicalRecord
)
when {
principal.department == resource.department &&
resource.assignedPhysician == principal &&
context.networkZone in ["hospital_internal", "trusted_vpn"] &&
context.mfaVerifiedWithinMinutes <= 120
};permit (
principal in Group::"Doctors",
action in [Action::"ReadRecord", Action::"WriteNote"],
resource is MedicalRecord
)
when {
principal.department == resource.department &&
resource.assignedPhysician == principal &&
context.networkZone in ["hospital_internal", "trusted_vpn"] &&
context.mfaVerifiedWithinMinutes <= 120
};
Cedar'ın syntax'ı XACML'den çok daha okunabilir, AWS IAM'den çok daha expressive. Tip güvenliği, policy validation, conflict detection gibi modern dil özelliklerine sahip. 2025'te ABAC dünyasının yükselen yıldızı.
Cedar'ın bir başka çekiciliği: policy analysis. "İki policy çakışıyor mu?", "Bu policy'nin allow ettiği bir resource var mı?" gibi sorular formal olarak cevaplanabiliyor. Klasik ABAC engine'lerin yapamadığı şey.
16.5. Tam Bir ABAC Evaluation Akışı
ABAC bir policy engine ile çalışır. Akış genelde şöyledir:
Bu akışın dikkat çekmek istediğim noktaları:
Birincisi: Policy engine bağımsız bir servis olabilir. Modern mimari (Bölüm 18'de OPA detayında göreceğiz), policy engine'i ayrı bir microservice ya da sidecar olarak çalıştırır. Uygulama her authorization sorusunu engine'e sorar; engine policy'leri değerlendirir, allow/deny döner.
İkincisi: PIP (Policy Information Point) çağrıları. Engine, karar verebilmek için subject, resource, environment attribute'larına ulaşmak zorunda. Bu çağrılar performans-kritik — her authorization isteğinde DB lookup yapmak istemiyoruz. Modern engine'ler caching ve attribute push stratejileriyle bunu optimize eder.
Üçüncüsü: Multi-source attribute aggregation. Subject attribute'ları IdP'den (JWT claim'lerinden), resource attribute'ları uygulama DB'sinden, environment attribute'ları runtime'dan geliyor. Engine bunları bir araya getirip tutarlı bir karar verir.
16.6. RBAC + ABAC = Hibrit Pattern (Modern Best Practice)
Modern authorization tasarımının en önemli realizasyonu şudur: RBAC ve ABAC, birbirinin alternatifi değil; tamamlayıcılarıdır. Modern bir sistem her ikisini birleştirir.
Bu hibrit yaklaşımın mantığı şöyledir:
- RBAC kategorik, kurumsal kararları yönetir: "Bu kullanıcı admin mı, user mı, viewer mı?" — kullanıcı sınıflandırması
- ABAC spesifik, contextual kararları yönetir: "Bu kullanıcı, bu specific resource'u, bu specific context'te erişebilir mi?" — runtime'da koşul kontrolü
Klasik bir hibrit policy:
permit (
principal,
action == Action::"ReadRecord",
resource is MedicalRecord
)
when {
// RBAC katmanı: temel rol kontrolü
principal in Role::"Doctor"
// ABAC katmanı: spesifik koşullar
&& principal.department == resource.department
&& context.networkZone == "hospital_internal"
};permit (
principal,
action == Action::"ReadRecord",
resource is MedicalRecord
)
when {
// RBAC katmanı: temel rol kontrolü
principal in Role::"Doctor"
// ABAC katmanı: spesifik koşullar
&& principal.department == resource.department
&& context.networkZone == "hospital_internal"
};Bu policy "doktor rolüne sahip" der (RBAC), sonra "ek koşullar" der (ABAC). İkisi birlikte modern authorization'ın tam yapısını verir.
Hibrit pattern'in avantajları:
RBAC'ın hızı korunuyor: Çoğu authorization kararı role kontrolü ile hızlıca filtreleniyor. ABAC sadece role kontrolü geçen istekler için detaylı değerlendiriyor.
ABAC'ın esnekliği ekleniyor: Context-sensitive kurallar (departman match, time, network) RBAC'ın doğal ifade edemediği şeylerle örtüşüyor.
Audit ve compliance kolaylığı: "Hangi rol ne yapabilir" hâlâ basit cevaplanabilir (RBAC). "Bu specific incident'te authorization niye allow oldu" detaylı izlenebilir (ABAC policy trace).
Gradual migration: Sistemin RBAC'tan ABAC'a tek seferde geçmesi gerekmiyor. Önce RBAC ile başla, sonra ABAC kuralları ekleyerek refine et.
16.7. Pratik — Spring Security ile ABAC
Spring Security 6.x, ABAC için doğal bir mekanizma sunar: SpEL (Spring Expression Language) içinde method security. @PreAuthorize annotation'ı sadece basit role kontrolü değil; karmaşık ABAC ifadeleri taşıyabilir.
@RestController
@RequestMapping("/api/medical-records")
public class MedicalRecordController {
private final MedicalRecordService service;
/**
* RBAC + ABAC hibrit:
* - Role kontrolü (hasRole)
* - Department match (custom check)
* - Assigned physician check (custom check)
*/
@GetMapping("/{recordId}")
@PreAuthorize("""
hasRole('DOCTOR')
and @medicalRecordPolicy.canRead(#recordId, authentication)
""")
public MedicalRecord getRecord(@PathVariable UUID recordId) {
return service.findById(recordId);
}
/**
* Write için ek koşullar: trusted network zone + recent MFA
*/
@PutMapping("/{recordId}")
@PreAuthorize("""
hasRole('DOCTOR')
and @medicalRecordPolicy.canWrite(#recordId, authentication, #request)
""")
public MedicalRecord updateRecord(
@PathVariable UUID recordId,
@RequestBody UpdateRecordRequest body,
HttpServletRequest request) {
return service.update(recordId, body);
}
}
/**
* Centralized ABAC policy evaluator.
* Tüm medical record authorization mantığı burada.
*/
@Component("medicalRecordPolicy")
public class MedicalRecordPolicy {
private final MedicalRecordRepository recordRepository;
private final UserAttributeService userAttributes;
private final NetworkZoneResolver networkResolver;
/**
* Read policy:
* - Doktor olmalı (zaten @PreAuthorize'da role check var)
* - Departman match
* - Assigned physician ya da consultation listesinde
*/
public boolean canRead(UUID recordId, Authentication auth) {
UUID userId = UUID.fromString(auth.getName());
// Subject attributes
UserAttributes user = userAttributes.get(userId);
// Resource attributes
MedicalRecord record = recordRepository.findByIdLite(recordId);
if (record == null) return false;
// ABAC condition: department match
if (!Objects.equals(user.getDepartment(), record.getDepartment())) {
return false;
}
// ABAC condition: assigned ya da consultation
boolean isAssigned = userId.equals(record.getAssignedPhysicianId());
boolean isConsultant = record.getConsultationDoctorIds().contains(userId);
return isAssigned || isConsultant;
}
/**
* Write policy: read'in üstüne ek koşullar.
*/
public boolean canWrite(UUID recordId, Authentication auth,
HttpServletRequest request) {
// Önce read koşulları sağlanmalı
if (!canRead(recordId, auth)) return false;
// Environment attributes
String networkZone = networkResolver.resolve(request);
if (!Set.of("hospital_internal", "trusted_vpn").contains(networkZone)) {
return false;
}
// MFA freshness: son 2 saat içinde MFA yapılmış olmalı
Jwt jwt = (Jwt) auth.getPrincipal();
Instant mfaVerifiedAt = jwt.getClaimAsInstant("mfa_verified_at");
if (mfaVerifiedAt == null
|| Duration.between(mfaVerifiedAt, Instant.now()).toMinutes() > 120) {
return false;
}
return true;
}
}@RestController
@RequestMapping("/api/medical-records")
public class MedicalRecordController {
private final MedicalRecordService service;
/**
* RBAC + ABAC hibrit:
* - Role kontrolü (hasRole)
* - Department match (custom check)
* - Assigned physician check (custom check)
*/
@GetMapping("/{recordId}")
@PreAuthorize("""
hasRole('DOCTOR')
and @medicalRecordPolicy.canRead(#recordId, authentication)
""")
public MedicalRecord getRecord(@PathVariable UUID recordId) {
return service.findById(recordId);
}
/**
* Write için ek koşullar: trusted network zone + recent MFA
*/
@PutMapping("/{recordId}")
@PreAuthorize("""
hasRole('DOCTOR')
and @medicalRecordPolicy.canWrite(#recordId, authentication, #request)
""")
public MedicalRecord updateRecord(
@PathVariable UUID recordId,
@RequestBody UpdateRecordRequest body,
HttpServletRequest request) {
return service.update(recordId, body);
}
}
/**
* Centralized ABAC policy evaluator.
* Tüm medical record authorization mantığı burada.
*/
@Component("medicalRecordPolicy")
public class MedicalRecordPolicy {
private final MedicalRecordRepository recordRepository;
private final UserAttributeService userAttributes;
private final NetworkZoneResolver networkResolver;
/**
* Read policy:
* - Doktor olmalı (zaten @PreAuthorize'da role check var)
* - Departman match
* - Assigned physician ya da consultation listesinde
*/
public boolean canRead(UUID recordId, Authentication auth) {
UUID userId = UUID.fromString(auth.getName());
// Subject attributes
UserAttributes user = userAttributes.get(userId);
// Resource attributes
MedicalRecord record = recordRepository.findByIdLite(recordId);
if (record == null) return false;
// ABAC condition: department match
if (!Objects.equals(user.getDepartment(), record.getDepartment())) {
return false;
}
// ABAC condition: assigned ya da consultation
boolean isAssigned = userId.equals(record.getAssignedPhysicianId());
boolean isConsultant = record.getConsultationDoctorIds().contains(userId);
return isAssigned || isConsultant;
}
/**
* Write policy: read'in üstüne ek koşullar.
*/
public boolean canWrite(UUID recordId, Authentication auth,
HttpServletRequest request) {
// Önce read koşulları sağlanmalı
if (!canRead(recordId, auth)) return false;
// Environment attributes
String networkZone = networkResolver.resolve(request);
if (!Set.of("hospital_internal", "trusted_vpn").contains(networkZone)) {
return false;
}
// MFA freshness: son 2 saat içinde MFA yapılmış olmalı
Jwt jwt = (Jwt) auth.getPrincipal();
Instant mfaVerifiedAt = jwt.getClaimAsInstant("mfa_verified_at");
if (mfaVerifiedAt == null
|| Duration.between(mfaVerifiedAt, Instant.now()).toMinutes() > 120) {
return false;
}
return true;
}
}Bu yaklaşımın altını çizmek istediğim üç tasarım kararı var.
Birincisi: ABAC mantığı centralized policy class'larda toplanıyor (
MedicalRecordPolicy). Her endpoint'in kendi içine custom logic yazmak yerine, tek bir yerden çağrılıyor. Test edilebilir, debug edilebilir, audit edilebilir.
İkincisi: SpEL ile referans çağırma (
@medicalRecordPolicy.canRead(...)). Bu, Spring Security'nin annotation tabanlı authorization ile complex ABAC policy'leri birleştirmenin temiz yolu.
Üçüncüsü: Layered policy —
canWrite,canRead'ı çağırıyor ve üstüne ek koşullar koyuyor. Write için "okuyabilen, üstüne trusted network, üstüne fresh MFA" zinciri. Bu pattern, ABAC policy'lerin kompozisyonel olmasını sağlar.
Spring Security 6.x'in daha yeni API'sı AuthorizationManager ile bu pattern daha da elegant ifade edilebilir:
@Bean
public AuthorizationManager<RequestAuthorizationContext> medicalRecordReadAuthz(
MedicalRecordPolicy policy) {
return (authentication, context) -> {
HttpServletRequest req = context.getRequest();
UUID recordId = UUID.fromString(req.getPathParameter("recordId"));
boolean granted = policy.canRead(recordId, authentication.get());
return new AuthorizationDecision(granted);
};
}@Bean
public AuthorizationManager<RequestAuthorizationContext> medicalRecordReadAuthz(
MedicalRecordPolicy policy) {
return (authentication, context) -> {
HttpServletRequest req = context.getRequest();
UUID recordId = UUID.fromString(req.getPathParameter("recordId"));
boolean granted = policy.canRead(recordId, authentication.get());
return new AuthorizationDecision(granted);
};
}Bu modern API, hem programmatic flexibility hem de Spring Security'nin filter chain entegrasyonunu sağlar.
16.8. ABAC'in Modern Policy Engine'ları
ABAC, kendi içinde bir engine ekosistemi yaratmıştır. Modern bir mimar olarak, hangi engine'in hangi senaryoda uygun olduğunu bilmek gerek.
AWS Cedar (2023, open source): Modern ABAC'in altın standardı yolunda. Type-safe DSL, formal analysis capability, AWS dünyasında yerleşik. Self-hosted da kullanılabilir (Cedar Java SDK var). Spring Boot entegrasyonu mümkün —
cedar-javalibrary ile.
Open Policy Agent (OPA) (CNCF, 2018): Genel amaçlı policy engine, sadece auth değil — infrastructure, deployment policy'leri de. Rego dili. Kubernetes ekosistemiyle entegre. Bölüm 18'in konusu, derinlemesine işleyeceğiz.
AWS IAM Policies: AWS native ABAC. AWS resource'ları için zorunlu, dışarıya transfer edilemez. Sadece AWS dünyasında çalışırsanız tek seçenek.
Azure Policy / Azure Conditional Access: Microsoft ekosistemi için ABAC. Conditional Access özellikle modern (cihaz uyumluluk, risk-based access gibi attribute'lar).
Google Cloud IAM Conditions: GCP'nin ABAC katmanı. AWS IAM benzeri ama daha sınırlı expression language.
Casbin (golang/multi-language): Açık kaynak, çok dilli access control library. RBAC + ABAC + ReBAC karma. Hafif sistemler için.
Oso (Polar dili): Modern, developer-friendly authorization library. Türk geliştirici topluluğunda da takip ediliyor.
Permify: Türk geliştirici tarafından kurulan, Google Zanzibar-style ReBAC ürünü (Bölüm 17'de detay).
Karar matrisi olarak:
Modern bir yeni proje için pratik öneri: Cedar (self-hosted, Spring entegrasyonu) ya da OPA (sidecar pattern). XACML legacy compatibility için tutulur; AWS IAM AWS'e bağımlısınız diye kullanılır.
16.9. ABAC vs RBAC — Trade-offs
İki modeli yan yana koyalım. Hangisinin nerede güçlü olduğunu görmek, hibrit kullanımı doğru tasarlamamızı sağlar.
ABAC'in fitness'ı:
- Context-sensitive kurallar gerekli (time, location, device)
- Resource-instance bazlı yetki (sadece kendi kayıtlarını okuyabilir)
- Dynamic policy (regulation değiştiğinde policy güncellenir, role değişmez)
- Multi-attribute karar (departman + clearance + project gibi kombinasyonlar)
RBAC'in fitness'ı:
- Basit kullanıcı sınıflandırması yeterli
- Hızlı authorization decision kritik
- Easy onboarding öncelikli
- Audit/compliance simple görmek gerek
Modern öneri: RBAC ile başla (kategorik karar), ABAC ile genişlet (contextual koşullar). İkisini birbirinin yerine değil, üst üste koy.
16.10. Anti-Patterns — ABAC Tasarımında Yaşayan Yanılgılar
Yanılgı 1: RBAC Olmadan Saf ABAC
ABAC sınıfsal kararı kaybeder; her authorization kararı için tüm attribute setini değerlendirmek gerek. "Bu kullanıcı admin" gibi basit gerçek bile karmaşık ABAC evaluation'a dönüşür. Modern öneri: RBAC + ABAC hibrit.
Yanılgı 2: Çok Karmaşık Policy'ler
Bir policy 30 condition içeriyorsa, onu kimse anlamıyor, debug etmek imkânsız. Policy basitlik prensibi: tek bir policy 5–7 condition'dan fazla olmamalı. Karmaşık mantık birden fazla policy'ye bölünmeli.
Yanılgı 3: Tutarsız Attribute Source'ları
user.department JWT claim'inden geliyor; ama başka bir endpoint'te DB'den çekilen user.department farklı değer döndürüyor. Karar tutarsızlığı doğar. Attribute source'ları konsolide edilmeli.
Yanılgı 4: Policy Versioning Yok
Policy'ler kodlanmadan deploy ediliyor. Bir bug'lı policy production'a sızdığında, geri almak imkânsız. Policy as Code + Git versiyonlama zorunluluk.
Yanılgı 5: Performance Göz Ardı
Her API isteğinde 5 DB lookup + complex expression evaluation. Sistem yavaşlıyor. Caching, attribute push, batch evaluation, prepared decision pattern'leri gerek.
Yanılgı 6: Policy Audit Trail Yok
"Bu kullanıcı niye reddedildi?" sorusuna cevap verilemiyor. Modern policy engine'ler decision log üretir; hangi policy hangi attribute'la nasıl evaluate oldu kaydedilir.
Yanılgı 7: Static Attribute'lar
Kullanıcının department'ı 6 ay önce DB'ye kayıt edildi; ama kullanıcı dün departman değiştirdi, DB update edilmedi. Authorization eski bilgiyle yapılıyor. Attribute lifecycle yönetimi gerek.
Yanılgı 8: Privacy Bilinçsiz
Policy attribute olarak user.health_condition kullanıyor. Authorization log'ları PII içerir. GDPR/KVKK ihlali. Sensitive attribute'lar hash'lenmiş ya da reference olarak taşınmalı.
Yanılgı 9: Policy Testing Yok
Bir policy yazılıyor, "doğru çalışıyor mu" sadece manual test ile kontrol ediliyor. Production'da edge case fail oluyor. Policy test framework (Cedar, OPA destekliyor) zorunluluk.
Yanılgı 10: UI'a Yansıtmama
Kullanıcı reddedildi, ama hangi koşulun sağlanmadığını bilmiyor. UI sadece "Forbidden" diyor. Modern UX: ABAC denial'ında "neden" sunulur (örneğin "sadece iş saatlerinde erişilebilir" mesajı).
16.11. Üç Gözlem Sorusu
Birinci soru. Şu anda çalıştığınız sistemde authorization mantığı nerede yaşıyor? Kod içinde dağınık if (user.role == ...) blokları mı, yoksa centralized bir policy mekanizması mı? Eğer kod-içinde dağılmışsa, bir policy refactor'unun değerini düşünün.
İkinci soru. Sisteminizde context-sensitive authorization kuralı var mı? Time-based, location-based, device-trust-based? Eğer "yok" cevabı geliyorsa, bunların eksikliği bir incident senaryosunu (gece yarısı admin erişimi, halka açık WiFi'dan admin login) kaçırmanız anlamına gelebilir mi?
Üçüncü soru. Eğer ABAC kullanıyorsanız, policy'lerin testi var mı? Bir policy'nin değişimi sonrasında, mevcut tüm "allow" ve "deny" kararlarının regression test'i yapılıyor mu? Yoksa "production'a deploy edip ne olacak görelim" mi? Bu, ABAC'in en sık başarısız olduğu yerdir.
16.12. Kapanış ve Bir Sonraki Adıma Köprü
Bu bölümde, RBAC'ın yetmediği yerleri dolduran ABAC modelini derinlemesine ele aldık. ABAC'ın felsefesini (rol değil, attribute'ların kombinasyonu), dört attribute kategorisini (subject, resource, action, environment), policy expression anatomisini, ve modern policy language'leri (XACML, AWS IAM, Cedar) yan yana koyduk. Tam bir ABAC evaluation akışını, attribute source'larının (PIP) ABAC'ın kalbinde nasıl yer aldığını gördük. Modern best practice olan RBAC + ABAC hibrit pattern'ini somutlaştırdık. Spring Security 6.x ile pratik bir ABAC implementation'ı, centralized policy class pattern'i, ve modern AuthorizationManager API'sini işledik. Modern policy engine'leri (Cedar, OPA, AWS IAM, Azure Conditional Access) karşılaştırdık. RBAC ile ABAC arasındaki trade-off'ları detaylı bir matriste sergiledik.
Bu bölümün altını çizmek istediğim mesaj: ABAC, modern authorization'ın "context-aware" boyutudur. Hastane senaryosu gibi sofistike koşulları, finansal sistemlerin SoD kurallarını, multi-tenant SaaS'ın tenant-scoped erişimlerini, modern compliance'ın time/location based kontrollerini — bunların hepsini ABAC zarif bir şekilde modeller. Ama ABAC tek başına yetmez; RBAC ile birlikte kullanılır. RBAC kategorik kararı sağlar (hızlı), ABAC contextual katmanı ekler (esnek). İkisinin doğru oranı, sistemin kompleksitesine bağlıdır.
Ama ABAC bile yetmediği bir senaryo daha var: resource ilişkileri. "Kullanıcı X bu document'in owner'ı", "Folder F bu user'a share edildi", "Manager Y bu employee'nin manager'ı" — bu tür relationship-based authorization, ABAC'ta da kompleks ifade edilir. Çünkü ABAC her resource için doğrudan attribute kontrolü yapar; ilişkisel graph traversal değil. Modern sistemlerin (Google Drive permission model, GitHub access control) ihtiyacı tam bu — graph-based permission modeling.
Bir sonraki bölüm olan Bölüm 17 — ReBAC: Relationship-Based Access Control ve Google Zanzibar, modern authorization'ın belki de en yeni paradigmasını işleyecek. Google'ın Drive, YouTube, Calendar gibi servislerinin arkasında çalışan Zanzibar sistemini, açık kaynak alternatifler olan SpiceDB, OpenFGA, Permify'i, ve ReBAC'in RBAC/ABAC ile nasıl birleştirilebileceğini göreceğiz. Authorization tasarımının bu üçüncü boyutu (RBAC kategorik + ABAC contextual + ReBAC ilişkisel) bir araya geldiğinde, modern sistemlerin authorization ihtiyacının %95'i karşılanır.
Hastanedeki "doktor mu?" sorusundan "hangi departmandan, hangi saatte?" sorusuna geldik. Şimdi "bu hastanın doktoru mu, yoksa konsültasyon yapan mı, yoksa hasta yakını yetkilendirdiği bir başka doktor mu?" sorusuna geçiyoruz.
"RBAC kategorik bir cevap verir; ABAC koşulu sorgular. Modern bir sistem, ikisini birleştirir — ama bazı sorular ne kategori ne koşul, ilişki sorularıdır. O sorulara ne RBAC ne ABAC cevap verir; başka bir paradigma gerektirir."
İçindekiler… « Önceki [Bölüm 15: Authorization Modelleri ve RBAC — Role-Based Access Control] » Sonraki [Bölüm 17: ReBAC — Relationship-Based Access Control ve Google Zanzibar] » https://gitlab.com/sahin.yelkenci2/auth-authorization-series