Django API'sini koruma

Bu hızlı başlangıç, Django API'nizi KeenAuth erişim belirteçleriyle korur. Python SDK'nın @protect görünüm süsleyicisini ekler, bir görünümde bir kapsam zorunlu kılar ve API'yi SDK test kiti ile çevrimdışı test edersiniz. Örnek Django 5.2 kullanır ve veritabanı gerektirmez.

API'nin üç uç noktası vardır:

Uç nokta Gereksinim
GET /api/public yok
GET /api/private API'niz için geçerli bir erişim belirteci
GET /api/messages messages:read kapsamına sahip geçerli bir erişim belirteci

Başlamadan önce

1. SDK'yı kurun

pip install "keenauth-python[django]"

SDK henüz yayımlanmadı. Yayımlanana kadar örnek, SDK'yı kaynak kodundan kurar. Örnek, her paketi requirements.txt içinde tam bir sürüme sabitler.

2. Yapılandırın

project/settings.py her değeri ortam değişkenlerinden okur:

Değişken Zorunlu Anlamı
DJANGO_SECRET_KEY evet Django'nun gizli anahtarı; bir tane üretin ve asla commit etmeyin
DJANGO_ALLOWED_HOSTS hayır Virgülle ayrılmış sunucu adları, varsayılan localhost,127.0.0.1
KEENAUTH_ISSUER evet Ortamınızın yayıncısı
KEENAUTH_AUDIENCE evet API'nizin tanımlayıcısı
PUBLIC_BASE_URL evet İstemcilerin çağırdığı URL (bkz. Proxy arkasında)
KEENAUTH_WEBHOOK_SECRET önerilir token.revoked teslim eden web kancası uç noktasının idp_whsec_ imza sırrı (döndürme sırasında virgülle ayrılmış iki değer). Bu olmadan API iptal edilmiş belirteçlerden haberdar olmaz

3. Görünümlerinizi koruyun

@protect(protector) geçerli bir belirteç ister ve doğrulanmış talepleri request.auth içine koyar; scopes=[...] ayrıca kapsamları da ister. Başarısız bir denetim, görünüm çalışmadan önce bir application/problem+json yanıtı döndürür.

@require_GET
def public(request: HttpRequest) -> JsonResponse:
    # No token needed.
    return JsonResponse({"message": "Hello from a public endpoint."})


@require_GET
@protect(protector)
def private(request: HttpRequest) -> JsonResponse:
    # Any valid access token for this API; the verified claims are in request.auth.
    return JsonResponse({"message": "Hello from a private endpoint.", "sub": request.auth.subject})


@require_GET
@protect(protector, scopes=["messages:read"])
def messages(request: HttpRequest) -> JsonResponse:
    # A valid access token that also carries the messages:read scope.
    return JsonResponse({"messages": ["Hello from a scoped endpoint."]})

api/auth.py süsleyiciyi başlangıçta ayarlardan (api/apps.py içindeki ApiConfig.ready) oluşturur; böylece eksik bir ayar, sunucu ilk isteği almadan önce durur. Doğrulayıcı, bir belirteci ilk gördüğünde imza anahtarlarını ortamınızdan alır.

@cache
def get_protector() -> Protector:
    if not settings.KEENAUTH_ISSUER or not settings.KEENAUTH_AUDIENCE or not settings.KEENAUTH_PUBLIC_BASE_URL:
        raise ImproperlyConfigured("KEENAUTH_ISSUER, KEENAUTH_AUDIENCE and PUBLIC_BASE_URL are required")
    # The verifier refuses an issuer under the reserved .invalid domain, so a test token can never pass here.
    verifier = Verifier(VerifierConfig(issuer=settings.KEENAUTH_ISSUER, audience=settings.KEENAUTH_AUDIENCE))
    return Protector(
        verifier,
        public_base_url=settings.KEENAUTH_PUBLIC_BASE_URL,
        revocation=revocation,
        # The error never contains the token or claim values; never log the Authorization or DPoP header.
        on_error=lambda err: log.warning("rejected: %s", err),
    )


@cache
def get_webhook() -> Optional[RevocationWebhook]:
    """The token.revoked webhook, or None when KEENAUTH_WEBHOOK_SECRET is not set. A malformed secret raises
    WebhookSecretError (without echoing it), so ApiConfig.ready stops the server at startup."""
    if not settings.KEENAUTH_WEBHOOK_SECRETS:
        return None
    return RevocationWebhook(settings.KEENAUTH_WEBHOOK_SECRETS, revocation)


class ProtectorProxy:
    """Hands each request to `get_protector()`, so the `@protect` decorators can be applied at import time."""

    def authenticate(self, req: RequestInfo, requirement: Optional[Requirement] = None) -> AccessClaims:
        return get_protector().authenticate(req, requirement)


protector = ProtectorProxy()

4. Çalıştırın ve API'yi çağırın

export DJANGO_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(50))')"
export KEENAUTH_ISSUER=https://your-environment.eu.keenauth.app
export KEENAUTH_AUDIENCE=https://api.example.com
export PUBLIC_BASE_URL=https://api.example.com
python manage.py runserver 8080

runserver yalnızca geliştirme içindir; üretimde project.wsgi komutunu bir WSGI sunucusu altında çalıştırın.

İlk çağrı için, API'nizde messages:read kapsamına sahip bir makineden makineye uygulamasının istemci kimlik bilgileri akışıyla bir belirteç alın:

ACCESS_TOKEN=$(curl -s -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials \
  -d resource=https://api.example.com \
  -d scope=messages:read \
  https://your-environment.eu.keenauth.app/oauth2/token | jq -r .access_token)

curl -i http://localhost:8080/api/messages -H "Authorization: Bearer $ACCESS_TOKEN"

5. Ağ olmadan test edin

keenauth.testing, genel bir test anahtarıyla imzalanmış belirteçler üretir ve testing.verifier yalnızca bu belirteçleri kabul eder. Testler get_protector yerine bu doğrulayıcıyı kullanan bir süsleyici koyar; bu yüzden ağa, KeenAuth hesabına veya ayarlarda bir test anahtarına ihtiyaç duymazlar. Bkz. Çevrimdışı test kiti.

class ProtectedAPITest(SimpleTestCase):
    def setUp(self) -> None:
        self.cache = MemoryRevocationCache()
        test_protector = Protector(testing.verifier(audience=AUDIENCE), public_base_url=BASE_URL, revocation=self.cache)
        patcher = mock.patch.object(auth, "get_protector", return_value=test_protector)
        patcher.start()
        self.addCleanup(patcher.stop)
    def test_scope(self) -> None:
        self.assert_problem(self.get("/api/messages", testing.mint_access_token(AUDIENCE)), 403, "insufficient_scope",
                            'scope="messages:read"')
        tok = testing.mint_access_token(AUDIENCE, claims={"scope": "profile messages:read"})
        self.assertEqual(self.get("/api/messages", tok).status_code, 200)
set -a; . ./.env.example; set +a
DJANGO_SECRET_KEY=insecure-test-only python manage.py test

Örneğin testleri, geçerli bir belirtecin kabul edildiğini ve eksik, süresi dolmuş veya yanlış hedef kitleli bir belirtecin, eksik bir kapsamın, URL sorgusundaki bir belirtecin ve iptal edilmiş bir belirtecin aşağıdaki durum kodlarıyla reddedildiğini denetler. Ayrıca PUBLIC_BASE_URL için bir kanıtla DPoP bağlı bir belirteç gönderirler ve üretim doğrulayıcısının test sağlayıcısını reddettiğini kontrol ederler. Testler ayrıca imzalı token.revoked ttesliminatlarını da gönderir: geçerli bir teslimat belirteci iptal eder; sahte, değiştirilmiş veya eski bir teslimat 400 alır ve hiçbir şeyi iptal etmez; tekrarlanan bir teslimatın tek bir etkisi olur.

Yanıtlar

İstek Durum Gövdede code
Authorization başlığı yok 401 missing_token
Süresi dolmuş belirteç, başka bir API için belirteç, hatalı imza, iptal edilmiş belirteç 401 invalid_token
Gerekli kapsamı olmayan belirteç 403 insufficient_scope
URL sorgusunda belirteç (?access_token=) 400 invalid_request

Gövde application/problem+json ve WWW-Authenticate başlığı standart sınamayı taşır; Django birden fazla sınamayı tek bir başlıkta birleştirir. Tam tablo Ara katmanla API'nizi koruma sayfasındadır.

Proxy arkasında

İstemciler DPoP kullanıyorsa, ara katman her kanıtın istemcinin çağırdığı URL için üretildiğini denetler. PUBLIC_BASE_URL değerini, örneğin https://api.example.com, istemcilerin çağırdığı dış URL olarak ayarlayın; bu, bir proxy veya yük dengeleyici arkasında da geçerlidir. Bu ayar zorunludur ve https kullanmalıdır (http yalnızca localhost, 127.0.0.1 veya [::1] için); bu ayar olmadan uygulama başlamayı reddeder. Ara katman X-Forwarded-* başlıklarını kendisi asla okumaz; Django bunları yalnızca SECURE_PROXY_SSL_HEADER veya USE_X_FORWARDED_HOST ayarladığınızda uygular ve PUBLIC_BASE_URL ayarlıyken denetlenen URL bunlara bağlı değildir.

İptal edilmiş belirteçler

Bir erişim belirteci süresi dolana kadar geçerli kalır; bu yüzden KeenAuth, belirteçlerin iptal edildiğini API'nize bildirir. Örnek, süsleyiciye bellek içi bir iptal önbelleği verir ve bunu besleyen bir token.revoked web kancasını POST /webhooks/keenauth (api/webhook.py) adresinde sunar.

  1. Panelde, https://<your host>/webhooks/keenauth URL'siyle, token.revoked olaylarına abone olan bir web kancası uç noktası oluşturun ve imza sırrını KEENAUTH_WEBHOOK_SECRET olarak dışa aktarın.
  2. İşleyici, imzayı SDK ile ham gövde üzerinde doğrular ve geçersizse ya da zaman damgası beş dakikadan fazla kaymışsa 400 yanıtı verir. Olayın seçicisini (access_token, session, user, client, org, org_user, client_user) bir iptal işaretçisine eşler ve 204 yanıtı verir; ara katman bunun ardından not_before öncesinde verilmiş eşleşen her belirteci reddeder.
  3. KeenAuth en az bir kez teslim eder; bu yüzden işleyici her webhook-id değerini bir kez uygular. İşaretçi saklanamazsa 503 yanıtı verir ve KeenAuth olayı yeniden teslim eder. Diğer olay türlerini ve hiçbir erişim belirtecinin eşleşemeyeceği seçicileri (refresh_family, api) yok sayar.

İşleyici olay kimliğini, seçici türünü ve nedeni günlüğe yazar; gövdeyi, imzayı veya sırrı asla yazmaz. Her çalışan süreç kendi önbelleğine sahiptir; birden fazla çalışan veya örnek varsa bunun yerine paylaşılan bir depo kullanın. Bkz. İptal önbelleği.

Sonraki adımlar