このガイドは標準ライブラリだけを使います。net/http と、プロセスで 1 つの http.Client です。このクライアントは接続を維持し、その Timeout は接続からボディの読み取りまで、呼び出し全体の上限になります。
タグ
新規登録、ログイン、チェックアウトの各ページを描画するレイアウトの <head> にタグを置きます。
<script src="https://korzen.dev/v1/tag.js" data-site-key="YOUR_SITE_KEY" async></script>
ページでフォームを送信する前に await window.korzen?.ready() を呼び出してください。タグが最後に報告してから訪問者が行ったことを報告し、何が起きても数秒以内に完了します。タグ
呼び出し
クライアントは標準ライブラリだけを使う 1 ファイル、korzen.go です。/v1/decide に問い合わせ、設定した timeout で打ち切り、エラーのときは必ず訪問者を通します。モジュール内のパッケージにダウンロードし、commit する前に中身を読んでください。
mkdir -p internal/korzen curl -o internal/korzen/korzen.go https://korzen.dev/bot-protection/sdk/korzen.go
Client はプロセスに 1 つです。接続を開いたまま保ち、Timeout が呼び出し全体の上限になります。Protect はアカウントを作成するハンドラーの前に問い合わせ、応答をリクエストの context に入れます。
var bot = &korzen.Client{APIKey: os.Getenv("KORZEN_API_KEY"), URL: "https://korzen.dev", Timeout: 200 * time.Millisecond} mux.Handle("POST /signup", bot.Protect(http.HandlerFunc(signup), korzen.Options{ IP: korzen.RemoteIP, // see "The visitor's IP" OnBlock: func(w http.ResponseWriter, r *http.Request, d korzen.Decision) bool { http.Error(w, "We could not create your account.", http.StatusBadRequest) return true }, // Your own check for challenge runs in signup: return false to go on to it. Without // OnChallenge, a challenged post is refused (403). OnChallenge: func(http.ResponseWriter, *http.Request, korzen.Decision) bool { return false }, })) func signup(w http.ResponseWriter, r *http.Request) { d, _ := korzen.FromContext(r.Context()) // no answer: the zero Decision if d.Action == korzen.Challenge { // your CAPTCHA, or an email or phone check, before going on } user, err := createAccount(r) // your own code // ... saveRequestID(user.ID, d.RequestID) // see "Keep the request_id" }
ハンドラーの中で問い合わせるなら d, err := bot.Decide(r.Context(), korzen.FromHTTP(r, korzen.RemoteIP(r))) です。エラーのときは d.Action が allow になり、err がログ用に理由を示します。action が allow、challenge、block のいずれでもない応答もエラーとして扱います。エラー応答の *korzen.Problem は err に含まれます(errors.As)。すべての呼び出しをログに残すには、クライアントの OnAnswer を設定してください。ステータス、Korzen-Request-Id、RateLimit ヘッダーを受け取ります。
訪問者の IP
r.RemoteAddr は接続元のアドレスで、プロキシがあればプロキシのアドレスになります。korzen.RemoteIP はこれを読みます。サーバーの前に何もないときに使ってください。korzen.CloudflareIP は CF-Connecting-IP を読みますが、これが正しいのはサーバーが Cloudflare からのトラフィックしか受け付けない場合だけです。そうでなければ、訪問者がこのヘッダーを書き込めてしまいます。自前のロードバランサーの背後では、korzen.LastForwardedIP が X-Forwarded-For の最後の値、つまりロードバランサーが追加した値を取ります。
アクションを適用する
読むのは action だけです。block の場合は、その操作がほかの理由で失敗したときと同じエラーを返し、bot に何も悟らせないようにします。challenge の場合は、すでにある確認手段(CAPTCHA、メールや電話の確認コード)を使います。何もなければそのまま続けます。アカウントは monitor mode で始まり、その間 action は常に allow です。block mode をオンにするまで、訪問者にとっては何も変わりません。または、訪問者を challenge.url のチャレンジページに送ってください。チャレンジページを参照してください。
OnChallenge を指定しない場合、middleware は challenge を通しません。ページ読み込み(GET)はチャレンジページへ送られ、フォームの送信や API の呼び出しは 403 で拒否されます。フォームを表示するページも保護してください。人はそこでチャレンジを受け、その通過によってフォームの送信が通ります。独自の確認手段がある場合は、OnChallenge を指定してそこで実行してください。戻り先は訪問者が開いたアドレスで、プロキシが付けていれば X-Forwarded-Host と X-Forwarded-Proto から読みます。Host: localhost を転送し、どちらも付けないプロキシ(nginx の既定)の後ろでは PublicURL: "https://shop.example" を指定してください。
request_id を保存する
すべての応答には request_id が含まれます。操作で作成されたもの(アカウントや注文)のカラムに保存するか、その ID と一緒にログに残してください。お問い合わせの際にその判定を特定でき、後からどの応答が偽アカウントを通したのかを調べられます。アカウントが良いか悪いかが分かったら、それを送り返してください:bot.Feedback(ctx, []korzen.Feedback{{RequestID: id, Outcome: "chargeback"}}, korzen.Batch{})(フィードバックを参照)。
手元のマシンでテストする
go run . を実行し、localhost でサーバーを開きます。localhost 上のページは常にタグを受け付け、ドメインとして一覧に載ることはありません。ローカルでは IP が 127.0.0.1 または ::1 になりますが、問題ありません。自分のマシンの Docker でアプリを動かすと、代わりにブリッジのアドレス(172.17.0.1 など)になりますが、タグがそこであなたのページを見ているので、チャレンジページは localhost へ戻してくれます。
- 環境変数に
KORZEN_API_KEYを設定し、新規登録ページを開きます。数秒後、ブラウザーの開発者ツールに_kzCookie が表示されます。 - 新規登録します。判定が dashboard の判定ページに、送信したパスで表示されます。
- fail-open を確認します。誤ったキーを設定してもう一度新規登録すると、呼び出しは
401を返し、新規登録はそのまま完了します。
よくある間違い
- 呼び出しごとに新しい
http.Transportを持つHTTPClient、または接続を 2 つしか維持しない標準ライブラリの既定のもの。新しい接続はそのたびに新しい TLS 接続です。HTTPClientを空のままにするか、プロセスに 1 回だけkorzen.NewTransport()を渡してください。 - Cloudflare を前段に置いていないサーバーで
CF-Connecting-IPを読む。bot がこのヘッダーを書き込み、自分の IP を好きに選べてしまいます。 - ブラウザーから
/v1/decideを呼び出す。API key がすべての訪問者に渡ってしまいます。呼び出しはサーバーで行ってください。 - タイムアウトがない、またはエラーで新規登録が止まる。
200以外の応答や、時間内に応答がない場合は、訪問者を通すという意味です。 - ログに
korzen: the visitor's IP sent to Bot Protectionで始まる行が出る。クライアントは、IP があなた自身のネットワークのもの(ロードバランサー、プロキシ、コンテナのもの)である最初の判定で一度だけこれを書きます。Bot Protection はその判定を IP なしとして扱います。Options.IPに訪問者のアドレスを設定してください(訪問者の IP)。自分のマシンの Docker ではブリッジのアドレスで、そこでは問題ありません。