Keycloak ile Merkezi Kimlik Yönetimi: OAuth ve JWT Token Rehberi
Büyük bir ekipte çalışıyorsunuz, onlarca mikroservis var, her birinin kendi kullanıcı yönetimi sistemi mevcut. Bir kullanıcı şifresini değiştirdiğinde beş farklı servise gidip güncelleme yapması gerekiyor. Güvenlik açığı mı çıktı? Bütün sistemlerdeki token’ları tek tek geçersiz kılmaya çalışıyorsunuz. Bu kaos ortamından çıkmanın en sağlıklı yolu merkezi kimlik yönetimi, yani Identity Provider (IdP) kullanmak. Keycloak tam da bu noktada devreye giriyor.
Keycloak Nedir ve Neden Kullanmalısınız
Keycloak, Red Hat tarafından geliştirilen açık kaynaklı bir Identity and Access Management (IAM) çözümü. OAuth 2.0, OpenID Connect ve SAML 2.0 protokollerini destekliyor. Kendi başına bir kullanıcı veritabanı tutabiliyor, mevcut LDAP veya Active Directory ile entegre olabiliyor, sosyal login (Google, GitHub, vb.) destekliyor.
Pratik açıdan bakarsak şu sorunları çözüyor:
- Single Sign-On (SSO): Kullanıcı bir kez giriş yapıyor, tüm uygulamalara erişiyor
- Merkezi kullanıcı yönetimi: Bir yerden kullanıcı ekle, sil, rol ata
- Token yönetimi: Access token, refresh token, JWT üretimi ve doğrulaması
- MFA desteği: TOTP, WebAuthn gibi ikinci faktör doğrulama
- Fine-grained authorization: Rol bazlı ve kural bazlı yetkilendirme
Kurumsal ortamlarda Keycloak’u genellikle şu senaryolarda görüyorsunuz: Birden fazla mikroservis arasında ortak kimlik doğrulama, frontend SPA uygulamaları için OAuth flow, B2B entegrasyonlarda federasyon.
Kurulum ve İlk Yapılandırma
Production ortamı için Docker Compose ile başlamak en pratik yol. Ama önce PostgreSQL backend’i kurmak şart, production’da H2 veritabanını asla kullanmayın.
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: guclu_sifre_buraya
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- keycloak_net
keycloak:
image: quay.io/keycloak/keycloak:23.0
command: start
environment:
KC_HOSTNAME: auth.sirket.com
KC_HOSTNAME_STRICT: false
KC_HTTP_ENABLED: true
KC_PROXY: edge
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin_sifresi_guclu_olmali
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: guclu_sifre_buraya
ports:
- "8080:8080"
depends_on:
- postgres
networks:
- keycloak_net
volumes:
postgres_data:
networks:
keycloak_net:
driver: bridge
# Servisleri başlat
docker-compose up -d
# Log takibi
docker-compose logs -f keycloak
# Keycloak hazır olduğunda şu mesajı görürsünüz:
# Running the server in development mode
# Listening on: http://0.0.0.0:8080
İlk kurulumdan sonra http://localhost:8080/admin adresine gidip admin paneline giriş yapın.
Realm ve Client Yapılandırması
Keycloak’ta her şey Realm kavramı üzerine kurulu. Realm, izole bir kimlik yönetimi ortamı. Master realm’i yönetim için kullanın, uygulamalarınız için ayrı realm oluşturun.
Keycloak Admin CLI ile realm oluşturma:
# Admin CLI ile token al
TOKEN=$(curl -s -X POST
http://localhost:8080/realms/master/protocol/openid-connect/token
-H "Content-Type: application/x-www-form-urlencoded"
-d "username=admin"
-d "password=admin_sifresi_guclu_olmali"
-d "grant_type=password"
-d "client_id=admin-cli" | jq -r '.access_token')
# Yeni realm oluştur
curl -s -X POST
http://localhost:8080/admin/realms
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{
"realm": "sirket-prod",
"enabled": true,
"displayName": "Sirket Production",
"registrationAllowed": false,
"loginWithEmailAllowed": true,
"accessTokenLifespan": 300,
"ssoSessionMaxLifespan": 36000
}'
Client yapılandırması kritik. Her mikroservis veya frontend uygulaması için ayrı client oluşturmalısınız:
# Frontend SPA için client oluştur (public client)
curl -s -X POST
http://localhost:8080/admin/realms/sirket-prod/clients
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{
"clientId": "frontend-app",
"name": "Frontend Uygulamasi",
"enabled": true,
"publicClient": true,
"standardFlowEnabled": true,
"redirectUris": ["https://app.sirket.com/*"],
"webOrigins": ["https://app.sirket.com"],
"protocol": "openid-connect"
}'
# Backend API için client oluştur (confidential client)
curl -s -X POST
http://localhost:8080/admin/realms/sirket-prod/clients
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{
"clientId": "backend-api",
"name": "Backend API Servisi",
"enabled": true,
"publicClient": false,
"serviceAccountsEnabled": true,
"standardFlowEnabled": false,
"directAccessGrantsEnabled": false,
"protocol": "openid-connect"
}'
JWT Token Yapısı ve Doğrulama
Keycloak’ın ürettiği JWT token’ları decode edip içeriğini anlamak, sorun gidermede hayat kurtarır.
# Token al (password grant - sadece test için)
TOKEN_RESPONSE=$(curl -s -X POST
"http://localhost:8080/realms/sirket-prod/protocol/openid-connect/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "client_id=frontend-app"
-d "[email protected]"
-d "password=kullanici_sifresi"
-d "grant_type=password"
-d "scope=openid profile email")
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token')
REFRESH_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.refresh_token')
# Token'ı decode et (base64)
echo $ACCESS_TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq .
Token payload’ında şunları görürsünüz:
- sub: Kullanıcının unique ID’si (UUID formatında)
- realm_access.roles: Realm düzeyindeki roller
- resource_access: Client bazındaki roller
- exp: Token’ın geçerlilik süresi (Unix timestamp)
- iss: Token’ı yayınlayan (Keycloak URL’i)
Token doğrulama için Keycloak’ın public key’ini kullanmalısınız:
# Realm'in public key bilgilerini al
curl -s http://localhost:8080/realms/sirket-prod/.well-known/openid-configuration | jq .
# JWKS endpoint'inden public key al
curl -s http://localhost:8080/realms/sirket-prod/protocol/openid-connect/certs | jq .
# Token introspection ile doğrula
curl -s -X POST
"http://localhost:8080/realms/sirket-prod/protocol/openid-connect/token/introspect"
-H "Content-Type: application/x-www-form-urlencoded"
-u "backend-api:client_secret_buraya"
-d "token=$ACCESS_TOKEN" | jq .
Nginx ile API Gateway Entegrasyonu
Gerçek dünya senaryosuna geçelim. Önünde Nginx olan bir API gateway yapısında Keycloak token doğrulamasını nasıl yaparsınız?
lua-resty-openidc modülü bu iş için biçilmiş kaftan:
# Nginx OpenResty konfigürasyonu
# /etc/nginx/conf.d/api-gateway.conf
lua_package_path "/usr/local/lib/lua/?.lua;;";
lua_shared_dict discovery 1m;
lua_shared_dict jwks 1m;
server {
listen 80;
server_name api.sirket.com;
location /api/ {
# Token doğrulama
access_by_lua_block {
local opts = {
discovery = "http://keycloak:8080/realms/sirket-prod/.well-known/openid-configuration",
token_signing_alg_values_expected = { "RS256" },
accept_none_alg = false,
accept_unsupported_alg = false,
-- Bearer token modunda çalış
bearer_only = true,
}
local jwt, err = require("resty.openidc").bearer_jwt_verify(opts)
if err then
ngx.status = 401
ngx.header.content_type = "application/json"
ngx.say('{"error": "unauthorized", "message": "' .. err .. '"}')
return ngx.exit(401)
end
-- Rol kontrolü
local roles = jwt.realm_access and jwt.realm_access.roles or {}
local has_role = false
for _, role in ipairs(roles) do
if role == "api-user" then
has_role = true
break
end
end
if not has_role then
ngx.status = 403
ngx.say('{"error": "forbidden"}')
return ngx.exit(403)
end
-- Kullanıcı bilgilerini header olarak backend'e ilet
ngx.req.set_header("X-User-Id", jwt.sub)
ngx.req.set_header("X-User-Email", jwt.email)
}
proxy_pass http://backend-service:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Rol ve Permission Yönetimi
Keycloak’ta yetkilendirmeyi iki seviyede yapılandırabilirsiniz: Realm Roles ve Client Roles. Büyük sistemlerde Composite Roles çok işe yarıyor, yani bir role başka rolleri dahil edebilirsiniz.
# Realm role oluştur
curl -s -X POST
"http://localhost:8080/admin/realms/sirket-prod/roles"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{"name": "manager", "description": "Yonetici rolü"}'
curl -s -X POST
"http://localhost:8080/admin/realms/sirket-prod/roles"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{"name": "employee", "description": "Calisan rolü"}'
# Kullanıcıya rol ata
USER_ID="kullanici-uuid-buraya"
ROLE_ID=$(curl -s
"http://localhost:8080/admin/realms/sirket-prod/roles/manager"
-H "Authorization: Bearer $TOKEN" | jq -r '.id')
curl -s -X POST
"http://localhost:8080/admin/realms/sirket-prod/users/$USER_ID/role-mappings/realm"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d "[{"id": "$ROLE_ID", "name": "manager"}]"
Fine-Grained Authorization için Keycloak’ın Authorization Services özelliğini aktif etmeniz gerekiyor. Bu özellik ile sadece rol değil, kaynak bazında izin tanımlayabilirsiniz. Örneğin “Kullanıcı kendi oluşturduğu dokümanları silebilir” gibi bir kural.
Service Account ile Mikroservis İletişimi
Servisler arası iletişimde kullanıcı yok, iki backend servis birbiriyle konuşuyor. Bu durumda Client Credentials Grant kullanırsınız:
# Service account token alma (Python benzeri senaryo için bash örneği)
SERVICE_TOKEN=$(curl -s -X POST
"http://localhost:8080/realms/sirket-prod/protocol/openid-connect/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "client_id=backend-api"
-d "client_secret=BURAYA_CLIENT_SECRET"
-d "grant_type=client_credentials"
-d "scope=openid")
ACCESS=$(echo $SERVICE_TOKEN | jq -r '.access_token')
EXPIRES_IN=$(echo $SERVICE_TOKEN | jq -r '.expires_in')
echo "Token alindi, $EXPIRES_IN saniye gecerli"
# Bu token ile diger servise istek at
curl -s -X GET
"http://user-service:8001/api/users"
-H "Authorization: Bearer $ACCESS"
-H "Content-Type: application/json"
Servis token’larının süresi kısa tutulmalı (genellikle 60-300 saniye). Token yenileme mekanizmasını kodunuza mutlaka ekleyin, süresi dolan token ile istek atmaya devam etmeyin.
LDAP ve Active Directory Entegrasyonu
Kurumsal ortamların çoğunda mevcut bir AD yapısı var. Keycloak bunu User Federation özelliği ile entegre edebiliyor:
# LDAP user federation ekleme
curl -s -X POST
"http://localhost:8080/admin/realms/sirket-prod/components"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{
"name": "sirket-ldap",
"providerId": "ldap",
"providerType": "org.keycloak.storage.UserStorageProvider",
"config": {
"vendor": ["ad"],
"connectionUrl": ["ldap://dc.sirket.com:389"],
"bindDn": ["CN=keycloak-svc,OU=ServiceAccounts,DC=sirket,DC=com"],
"bindCredential": ["svc_account_sifresi"],
"usersDn": ["OU=Users,DC=sirket,DC=com"],
"usernameLDAPAttribute": ["sAMAccountName"],
"rdnLDAPAttribute": ["cn"],
"uuidLDAPAttribute": ["objectGUID"],
"userObjectClasses": ["person", "organizationalPerson", "user"],
"searchScope": ["2"],
"useTruststoreSpi": ["ldapsOnly"],
"syncRegistrations": ["false"],
"importEnabled": ["true"],
"batchSizeForSync": ["1000"],
"fullSyncPeriod": ["604800"],
"changedSyncPeriod": ["86400"]
}
}'
LDAP entegrasyonunda dikkat edilmesi gerekenler:
- bindDn için kullandığınız service account’a minimum yetkiler verin, sadece read permission yeterli
- fullSyncPeriod çok kısa tutmayın, büyük AD ortamlarında yük bindirir
- changedSyncPeriod ile delta sync yaparak değişiklikleri daha sık çekin
- LDAP bağlantısı koptuğunda Keycloak’ın davranışını test edin, cache mekanizmasını anlayın
Token Refresh ve Session Yönetimi
Production’da en çok sorun yaşanan konulardan biri token yenileme stratejisi. Access token süresini çok kısa tutarsanız refresh isteği yükü artar, çok uzun tutarsanız güvenlik riski oluşur.
# Refresh token ile yeni access token al
NEW_TOKEN=$(curl -s -X POST
"http://localhost:8080/realms/sirket-prod/protocol/openid-connect/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "client_id=frontend-app"
-d "grant_type=refresh_token"
-d "refresh_token=$REFRESH_TOKEN")
echo $NEW_TOKEN | jq '{
access_token: .access_token[:50],
expires_in: .expires_in,
refresh_expires_in: .refresh_expires_in
}'
# Kullanıcıyı logout yap (session'ı sonlandır)
curl -s -X POST
"http://localhost:8080/realms/sirket-prod/protocol/openid-connect/logout"
-H "Content-Type: application/x-www-form-urlencoded"
-d "client_id=frontend-app"
-d "refresh_token=$REFRESH_TOKEN"
# Admin olarak belirli kullanıcının tüm sessionlarını sonlandır
curl -s -X DELETE
"http://localhost:8080/admin/realms/sirket-prod/users/$USER_ID/sessions"
-H "Authorization: Bearer $TOKEN"
Önerilen token süreleri production için:
- Access token: 300 saniye (5 dakika)
- Refresh token: 1800 saniye (30 dakika) veya SSO session süresiyle bağlantılı
- SSO session max: 36000 saniye (10 saat)
Monitoring ve Troubleshooting
Keycloak’ı gözlemlemeden işletmek kör uçuş gibi. Prometheus metrics endpoint’i açın:
# Keycloak metrics aktif et (KC 22+ için)
# docker-compose environment'a ekle:
# KC_METRICS_ENABLED: true
# KC_HEALTH_ENABLED: true
# Metrics endpoint'ini kontrol et
curl -s http://localhost:8080/metrics | grep keycloak_logins
# Health endpoint
curl -s http://localhost:8080/health | jq .
curl -s http://localhost:8080/health/ready | jq .
curl -s http://localhost:8080/health/live | jq .
Yaygın sorunlar ve çözümleri:
- “PKCE required” hatası: Public client’larda PKCE zorunlu hale geldi, frontend kodunuzu güncelleyin
- “Invalid redirect URI” hatası: Client konfigürasyonundaki redirect URI ile birebir eşleşmeli, wildcard dikkatli kullanın
- Token doğrulama başarısız: Keycloak sunucu saatiyle uygulamanın saati arasında fark olabilir, NTP kontrolü yapın
- LDAP sync timeout:
batchSizeForSyncdeğerini düşürün, büyük AD ortamlarında 500 iyi bir başlangıç - Session yığılması:
offlineSessionsayarlarını gözden geçirin, eski oturumları temizleyin
Keycloak event log’larını takip edin, şüpheli giriş denemelerini izlemek için:
# Event log'larını al
curl -s
"http://localhost:8080/admin/realms/sirket-prod/events?type=LOGIN_ERROR&max=50"
-H "Authorization: Bearer $TOKEN" | jq '[.[] | {time: .time, ipAddress: .ipAddress, error: .error}]'
Yüksek Erişilebilirlik için Cluster Kurulumu
Tek node Keycloak production için yeterli değil. Infinispan (JGroups) ile cluster kurulumu yapmanız gerekiyor:
# Cluster modunda başlatma
# docker-compose'a şunları ekleyin:
# keycloak servisine:
command: start --optimized
environment:
KC_CACHE: ispn
KC_CACHE_STACK: kubernetes
JAVA_OPTS_APPEND: >-
-Djgroups.dns.query=keycloak-headless
-Dkeycloak.connectionsInfinispan.remoteStoreEnabled=false
Cluster ortamında dikkat edilmesi gerekenler:
- Sticky session yerine distributed cache kullanın
- Load balancer’da HTTPS termination yapın, Keycloak’a plain HTTP iletmek için
KC_PROXY: edgeayarlayın - Her node için aynı
KC_HOSTNAMEdeğeri kullanın - Database connection pool boyutunu node sayısına göre ayarlayın
Güvenlik Sertleştirme
Keycloak kurulumu sonrası bu güvenlik adımlarını atlamayın:
- Brute force koruması: Admin konsolundan Realm Settings > Security Defenses > Brute Force Detection aktif edin
- Password policy: Minimum uzunluk, karakter çeşitliliği, şifre geçmişi zorunluluğu ekleyin
- Master realm: Production’da master realm’e dışarıdan erişimi kapatın veya farklı port üzerinden yönetin
- HTTPS zorunluluğu:
sslRequired: allayarını yapın - Client secret rotation: Periyodik olarak client secret’ları döndürün
- Unused grant types: Kullanmadığınız grant type’ları (implicit flow gibi) kapatın
Sonuç
Keycloak ilk kurulumda biraz karmaşık görünüyor, ama bir kez doğru yapılandırdıktan sonra onlarca uygulamanın kimlik yönetimini tek noktadan hallediyorsunuz. Özellikle mikroservis mimarilerinde bu merkezileşme hem güvenliği artırıyor hem de operasyonel yükü ciddi ölçüde azaltıyor.
Başlangıç için önerdiğim yol: Önce tek realm, birkaç client ile küçük başlayın. Token akışlarını tam anlamadan Authorization Services gibi ileri özelliklere geçmeyin. LDAP entegrasyonunu test ortamında iyice deneyin, production’a geçmeden önce edge case’leri keşfedin.
Keycloak topluluğu aktif ve dokümantasyonu güçlü. Bir sorunla karşılaştığınızda Keycloak Discourse forumu genellikle ilk adresiniz olsun. Versiyon güncellemelerini takip edin, özellikle minor versiyonlarda bile konfigürasyon değişiklikleri olabiliyor. kcadm.sh CLI aracını öğrenirseniz otomasyon scriptleri yazmak çok kolaylaşıyor, bunu mutlaka araç kutunuza ekleyin.
