Ein Modul dient beiden: Es nimmt die IP, die Header der Anfrage und das _kz-Cookie, sodass Django und Flask jeweils ihre eigenen übergeben. Es verwendet requests mit einer Session pro Prozess, die die Verbindung offen hält; timeout gilt für die Verbindung und für jeden Lesevorgang.
Der Tag
Setzen Sie den Tag in den <head> Ihres Basis-Templates (base.html), das die Registrierungs-, Anmelde- und Checkout-Seiten erweitern:
<script src="https://korzen.dev/v1/tag.js" data-site-key="YOUR_SITE_KEY" async></script>
Der Aufruf
# korzen.py import os import requests korzen = requests.Session() # one per process: it keeps the connection open # The headers Bot Protection reads: those one browser build sends alike, and Accept-Language. FORWARDED = ["accept", "accept-encoding", "accept-language", "sec-ch-ua", "sec-ch-ua-mobile", "sec-ch-ua-platform", "sec-fetch-dest", "sec-fetch-mode", "sec-fetch-site", "upgrade-insecure-requests"] def header_order(environ): """The order the request's headers came in, as gunicorn keeps it in the WSGI environ.""" names = [k for k in environ if k.startswith("HTTP_") or k in ("CONTENT_TYPE", "CONTENT_LENGTH")] return [k.removeprefix("HTTP_").lower().replace("_", "-") for k in names[:64]] def decide(ip, headers, environ, token, path): """The answer for this request, or None on any error: then go on as on allow (fail open).""" try: r = korzen.post( "https://korzen.dev/v1/decide", timeout=0.2, headers={"Authorization": f"Bearer {os.environ['KORZEN_API_KEY']}"}, json={ "ip": ip, "user_agent": headers.get("User-Agent"), "headers": {h: headers[h] for h in FORWARDED if h in headers}, "header_order": header_order(environ), "token": token, "path": path, "method": "POST", }, ) return r.json() if r.status_code == 200 else None except Exception: return None
header_order ist die Reihenfolge, in der die Header der Anfrage ankamen: Ein Skript, das die Werte eines Browsers kopiert, kopiert selten ihre Reihenfolge. gunicorn hält sie in den HTTP_*-Schlüsseln des WSGI-Environ (in Django request.META, in Flask request.environ) und verwirft einen Namen mit _. Hinter nginx, einem Load Balancer oder Cloudflare ist es die Reihenfolge, in der dieser Hop sie weitergibt, nicht die des Browsers.
In Django, in der View, die das Konto anlegt:
# views.py from django.shortcuts import redirect, render from . import korzen def client_ip(request): # Behind Cloudflare: return request.headers.get("CF-Connecting-IP"). # Behind one proxy of yours that appends to X-Forwarded-For: its last entry. forwarded = request.headers.get("X-Forwarded-For") if forwarded: return forwarded.split(",")[-1].strip() return request.META.get("REMOTE_ADDR") def signup(request): if request.method == "POST": d = korzen.decide(client_ip(request), request.headers, request.META, request.COOKIES.get("_kz"), "/signup") if d and d["action"] == "block": return render(request, "signup.html", {"error": "We could not create your account."}, status=400) # "challenge": your CAPTCHA, or an email or phone check, before going on user = create_account(request.POST) # your own code save_request_id(user, d and d["request_id"]) # see "Keep the request_id" return redirect("welcome") return render(request, "signup.html")
In Flask:
# app.py from flask import Flask, redirect, render_template, request from werkzeug.middleware.proxy_fix import ProxyFix import korzen app = Flask(__name__) app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1) # one proxy of yours in front @app.post("/signup") def signup(): d = korzen.decide(request.remote_addr, request.headers, request.environ, request.cookies.get("_kz"), "/signup") if d and d["action"] == "block": return render_template("signup.html", error="We could not create your account."), 400 # "challenge": your CAPTCHA, or an email or phone check, before going on user = create_account(request.form) # your own code save_request_id(user, d and d["request_id"]) # see "Keep the request_id" return redirect("/welcome")
Die IP des Besuchers
Django hat keine Einstellung für Proxys: REMOTE_ADDR ist die Adresse der Verbindung, die Ihres Proxys, falls Sie einen haben. client_ip oben nimmt den letzten Eintrag von X-Forwarded-For, den Ihr Proxy angehängt hat; mit zwei eigenen Proxys davor nehmen Sie den davor. Hinter Cloudflare lesen Sie CF-Connecting-IP. In Flask macht ProxyFix(x_for=1) dasselbe für einen Proxy und setzt request.remote_addr.
Die Aktion anwenden
Lesen Sie nur action. Bei block antworten Sie mit demselben Fehler, den die Aktion zeigt, wenn sie aus einem anderen Grund scheitert, damit ein Bot nichts lernt. Bei challenge nutzen Sie die Prüfung, die Sie schon haben (ein CAPTCHA, ein Code per E-Mail oder Telefon); ohne eine machen Sie weiter. Das Konto beginnt im Monitor-Modus, in dem action immer allow ist: Für Besucher ändert sich nichts, bis Sie den Block-Modus einschalten.
Die request_id aufbewahren
Jede Antwort hat eine request_id. Bewahren Sie sie bei dem auf, was die Aktion erstellt hat, in einer Spalte des Kontos oder der Bestellung, oder in einer Logzeile mit deren ID. Sie benennt diese Entscheidung, wenn Sie uns dazu etwas fragen, und lässt Sie später herausfinden, welche Antwort ein Fake-Konto durchgelassen hat.
Auf Ihrem Rechner testen
Führen Sie python manage.py runserver oder flask run aus und öffnen Sie es auf localhost. Seiten auf localhost akzeptieren den Tag immer und erscheinen nie als Domain. Lokal ist die IP 127.0.0.1 oder ::1, und das ist in Ordnung. Läuft die App auf Ihrem Rechner in Docker, sieht sie stattdessen die Adresse der Bridge, etwa 172.17.0.1: Die Challenge-Seite bringt Sie trotzdem zu localhost zurück, denn der Tag hat Ihre Seite dort gesehen.
- Setzen Sie
KORZEN_API_KEYin der Umgebung (oder in Ihrer.env, falls Sie eine laden) und öffnen Sie die Registrierungsseite. In den Entwicklertools des Browsers erscheint nach ein paar Sekunden das_kz-Cookie. - Registrieren Sie sich. Die Entscheidung erscheint auf der Seite Entscheidungen des Dashboards, unter dem Pfad, den Sie gesendet haben.
- Prüfen Sie fail-open: Setzen Sie einen falschen Schlüssel und registrieren Sie sich erneut. Der Aufruf antwortet
401, und die Registrierung geht durch.
Häufige Fehler
- Der erste Eintrag von
X-Forwarded-For. Der Besucher schreibt ihn, also wählt ein Bot seine eigene IP. ProxyFixmit einem höherenx_forals Ihre Proxys. Flask vertraut dann einem Eintrag, den der Besucher geschrieben hat.- Ein Formular, das JavaScript sendet, sobald die Seite lädt, bevor der Tag berichtet hat: Rufen Sie
await window.korzen?.ready()auf, bevor Sie es senden. /v1/decideaus dem Browser aufrufen. Der API key würde jeden Besucher erreichen; der Aufruf gehört auf Ihren Server.- Kein Timeout, oder ein Fehler, der die Registrierung stoppt. Jede andere Antwort als
200, und keine Antwort rechtzeitig, bedeutet: den Besucher durchlassen.