コードレビューで「認証不要の共有ページをどう守るか」を整理したときのメモを、勉強のためにまとめたものです。
この記事でわかること
- 認証不要の公開エンドポイントに必要な防御の考え方
- レートリミット(DRF Throttle)・ハッシュ保存・最低文字数の組み合わせ方
- ハッシュ照合が「タイミング攻撃」にも強い理由
概念
綴Photo には、写真をまとめたページをパスワード付きで共有する機能があります。これは「ログイン不要・URL(共有トークン)を知っていれば誰でも開ける」公開エンドポイントです。パスワード(アクセスコード)の検証を行うので、総当たり攻撃の標的になりやすいです。
綴Photo はこの公開エンドポイントを、3つの層で守っています。
守り①: レートリミット(DRF の ScopedRateThrottle)
綴Photo は Django REST framework(DRF)の ScopedRateThrottle を使い、共有ページ検証エンドポイントに専用の枠(10/min)を割り当てています。
from rest_framework.throttling import ScopedRateThrottle
class SharedPagePublicView(APIView):
permission_classes = [] # 認証不要(公開)
authentication_classes = []
throttle_classes = [ScopedRateThrottle]
throttle_scope = "shared_page_verify" # 下の設定で 10/min
# settings
DEFAULT_THROTTLE_RATES = {
"shared_page_verify": "10/min",
# ... 他のスコープ
}本番運用での注意が1つあります。Throttle のカウンタはキャッシュに乗ります。
綴Photo は当面 Render を1インスタンス+プロセス内メモリキャッシュ(LocMemCache)で運用し、スケールしたら共有キャッシュ(Redis)に切り替える、という判断を設定にコメントで残しています。
守り②: パスワードはハッシュで保存する(make_password / check_password)
共有ページのパスワードは「ただのアクセスコードだから平文で持っても…」と迷いやすいところです。
from django.contrib.auth.hashers import make_password, check_password
# 発行時: ハッシュ化して保存
shared.password_hash = make_password(raw_password)
# 検証時: ハッシュ照合
if not check_password(raw_password, shared.password_hash):
return Response({"detail": "パスワードが違います。"}, status=400)共有先は写真の公開 URL になりうる=守る対象が重いです。アクセスコード的な位置付けでも、平文で持つ理由はありません。
守り③: 最低文字数とサイズ上限
class SharedPageCreateSerializer(serializers.Serializer):
password = serializers.CharField(write_only=True, min_length=8, max_length=128)min_length=8: 短いコードはレートリミットがあっても総当たりの余地が残るので、入口で下限を引きますmax_length=128: 長すぎる文字列を送りつけてハッシュ計算を重くされる事故(DoS)を防ぐ上限です
タイミング攻撃について
「パスワード比較」と聞くと、== で比べたときのタイミング攻撃が気になるかもしれません。
ここは、守り②でハッシュ照合に切り替えた時点で解決しています。check_password は内部で定時間比較を行うため、平文を == で比べたときのような応答時間リークは起きません。自前で hmac.compare_digest を呼ぶ必要はありません(綴Photo では Webhook トークンの検証など、別の箇所で compare_digest を使っています)。
なぜ守り漏れが起きやすいか
公開エンドポイントの守りが抜けるのには、構造的な理由があります。ログイン処理のエンドポイントには反射的にレートリミットや文字数制限を入れるのに、「共有ページのパスワード照合」は "認証じゃない・ただの一致チェック" と脳内で分類されやすいです。その瞬間、防御の意識からすっぽり抜けます。
公開=認証なしのエンドポイントこそ、ログインの壁がない分、守りを「自分で明示的に設計する」必要があります。「これは認証じゃないから」と分類したエンドポイントが、いちばん盲点になりやすいです。
学びポイント
- どの層も単体では穴が残ります。3つをセットで設計します
- 公開 = 誰でもアクセスできる = 攻撃者も同じ立場です。「URL を知られなければ大丈夫」は防御ではありません
- ハッシュ照合(
check_password)は定時間比較なので、平文==の応答時間リークも同時に塞げます。「ハッシュにする」は保存の安全だけでなく比較の安全もついてきます - 「認証じゃない、ただの一致チェック」と分類したエンドポイントが盲点になりやすいです
おわりに
公開エンドポイントは「攻撃者と同じ立場の人が常に叩いている」前提で設計します。それぞれ単体では完璧ではないが、組み合わせて初めて「割に合わない攻撃対象」になります。
レートリミットは本番でカウンタの共有(キャッシュバックエンド)が要ること、ハッシュ照合なら定時間比較が無料でついてくること——どこで何を守っているかを一度棚卸しすると、抜けが見えます。
この記事は2026年4月時点の自分の理解で書いています。レートリミットの設定や本番運用の注意点は変化が速い領域なので、最新の公式ドキュメントも合わせて参照してください。