本文へ移動
実装と知見

1万人規模の会員サービスで実施しているセキュリティ対策

#セキュリティ#会員サービス#システム開発#ウェブ運用

会員サービスの本人確認、サーバー側の閲覧権限、会員データ、非公開画像、決済の役割を分け、それぞれの経路を確認する概念図

当社は1万人規模の会員サービスVoiceMind(新しいタブで開きます)を運営し、本人の声の分析結果や記録を扱っています。サービス提供者として、本人の情報を本人へ返すために、認証、閲覧権限、決済連携、画像配信、データベースの保守に対策を講じています。

ログインの本人確認はClerk、会員情報や記録はFastAPIのバックエンドとPostgreSQL、決済はStripe、非公開画像の保存はCloudflare R2が担う構成です。それぞれの製品の保護機能に加え、製品同士をつなぐ処理でも、誰の情報をどこまで返すかを制御しています。

本人確認からサーバー側の閲覧権限確認を経て、会員データと非公開画像を返す。カード入力は決済サービスが受け持つ概念図

対策で防ぎたいこと

会員サービスでは次のような事態を防ぐために対策を組み合わせます。

  • パスワードを平文で保存すると、保存内容を読まれた際に、ログインに使える文字列がそのまま渡ってしまう。
  • データの所有者を確認しないと、ログインできる人が他人の履歴や画像まで取得できてしまう。
  • 外部APIの応答を丸ごと返すと、画面に不要な請求情報までブラウザーへ送ってしまう。
  • 画像URLの期限やキャッシュを管理しないと、意図しない相手にURLが渡った際や、共有キャッシュを通じて画像を見られるおそれがある。
  • DBへの接続を制限しないと、アプリ側の権限確認を通らずにデータへアクセスされるおそれがある。
  • 依存部品の修正を取り込まないと、自社で書いたコードに問題がなくても、部品の脆弱性を悪用されるおそれがある。
  • バックアップと復旧手順が不十分だと、更新や障害のあとにデータを戻せず、停止が長引くおそれがある。

以下ではそれぞれに対して実施している対策と確認方法を紹介します。

パスワードの保持方法

VoiceMindの会員パスワードは認証基盤のClerkが管理し、自社の会員DBには保存していません。自社DBはClerkの利用者IDと会員情報を対応付ける構成です。Clerkの認証の仕組み(新しいタブで開きます)

パスワードを保存する際の基本は、平文を残さず、利用者ごとに異なる乱数(ソルト)を加え、専用のハッシュ関数で変換した値を保持することです。ログイン時には入力されたパスワードを同じ方法で処理し、保存した値と照合します。

照合用の値を作る計算にはあえて時間のかかる方式を使います。例えば1万通りのパスワードを試すには、この計算も1万回必要です。候補を試すたびに計算が必要になるため、短時間に大量のパスワードを試しにくくする仕組みです。OWASPのパスワード保存指針(新しいタブで開きます)

本人確認とデータの閲覧権限

認証は「誰が利用しているか」を確認する処理、認可は「その人に何を許可するか」を決める処理です。説明用のAさんとBさんがそれぞれログインできても、AさんがBさんの履歴を読めるわけではありません。

VoiceMindではサーバー側で検証したClerkのセッションから利用者を特定し、DB上の会員と対応させています。会員向けの履歴一覧は、その会員が所有するデータに絞って取得します。

リクエストに含まれる利用者の識別子を、認証済みの本人と照合する共通処理も設けています。食い違う場合は要求を拒否します。契約状態もブラウザーから渡された表示用の情報だけで決めず、バックエンドがDBを参照して判断する構成です。

記録の番号を推測しにくくすることと、その記録を読める人を確認することは、別々に必要です。OWASPも対象データごとの権限確認をAPIの重要な対策として挙げています。OWASP API Securityの解説(新しいタブで開きます)

権限を外れた要求のテスト

画像取得とURL発行では、最初から作ったダミーの利用者と画像キーを使い、次の動作をテストしています。

入力や状態 確認した動作
ログインしていない 非公開画像のURL発行・取得を拒否する
本人以外の画像を指定する URLを発行せず、ダウンロードを拒否する
本人の画像と他人の画像を一括で指定する 一括要求を拒否する
本人の識別子に似た別の文字列を指定する 本人の保存領域と取り違えない
不正な形式や期限を指定する 不正な入力として拒否する

ダウンロードの拒否では代替の保存処理に残る記録を使い、保存先への読み出しが発生していないことまで確かめました。許可する条件と拒否する条件を、処理ごとに確認するためのテストです。

Stripe連携で受け取る情報と返す情報

VoiceMindではカード入力や支払いにStripeのCheckoutとカスタマーポータルを利用しています。この決済経路ではカード番号全体やセキュリティコードを自社の会員APIで受け取り、保存する構成にしていません。Stripeの連携セキュリティガイド(新しいタブで開きます)

一方、権限を持つサーバーからのAPI利用では、カード番号の下4桁や有効期限などを取得できます。StripeのPaymentMethod仕様には、card.last4、card.exp_month、card.exp_yearが定義されています。請求先氏名を扱うbilling_details.nameは、値がない場合もある項目です。StripeのPaymentMethod仕様(新しいタブで開きます)

そこで、自社の会員向け請求状態APIでは必要な契約状態や変更可能な選択肢を返す項目として定義しています。この返却定義に、カードの下4桁、有効期限、請求先氏名は含めていません。外部APIから取得できる情報と、会員へ返す情報を分ける設計です。

契約の識別子、状態、決済通知の処理記録は、運営に必要な情報として管理しています。カード番号全体を保持しない構成でも、請求・契約情報の管理は自社の役割です。

Stripeから届く決済通知は、受信時にSDKでWebhookの署名を検証しています。送信元と改ざんの有無を確かめたうえで、対象の会員・契約と対応させる処理です。StripeのWebhook署名検証(新しいタブで開きます)

非公開画像のURL・期限・キャッシュ

VoiceMindでは本人の閲覧権限を確認してから、非公開画像の署名付きURLを発行しています。配信側でも署名と期限を検証し、画像を返す構成です。

署名付きURLは、有効期間中にURLを持つ人が使える方式です。そのため、通常の画像表示に使うURLは数分単位とし、発行元ごとに期限の上限を設けています。AWSによる署名付きURLの説明(新しいタブで開きます)

発行から配信まで、次の条件を組み合わせています。

  1. 発行前に、本人の画像であることを確認する。
  2. クライアントが指定する期限の型・値と上限を確認する。
  3. 配信側で、署名・期限・形式を検証する。
  4. 対象の非公開画像・URL返却・ダウンロードの応答にCache-Control: private, no-storeを設定する。
  5. 配信処理のログに、署名付きURLのクエリ文字列を残さない。

private, no-storeは、共有キャッシュへの保存を制限し、応答を保存しないよう指示する設定です。URLの期限と、キャッシュ・ログの制御を組み合わせて扱っています。HTTPキャッシュの標準仕様(新しいタブで開きます)

署名鍵はサービス側のSecretで管理し、ブラウザーへ渡しません。鍵の切替では、新しい鍵で検証できることと旧鍵での要求が拒否されることを確認しました。本番の配信確認には実在しない画像キーを使い、会員の画像を取得せずに署名の判定を確かめています。

PostgreSQLの接続制限と更新

DBはインターネットへ直接公開せず、接続待受、ファイアウォール、接続認証の規則を照合して接続範囲を管理しています。

アプリがDBへ接続するためのパスワードは、会員のログイン用とは別の資格情報です。対象の接続にはSCRAM方式の認証を使い、DB側には平文ではなくハッシュ処理した照合用の情報を保持しています。ローカルの管理用接続にはOS上の利用者を確認する認証を使っています。PostgreSQLのパスワード認証(新しいタブで開きます)

認証設定は稼働中の規則と照合し、共有DBを利用する各サービスが実際に使うドライバーで再接続を確認しました。接続先・ロール・稼働バージョンなどのメタデータを使い、利用者データの本文は読み取っていません。

共有DBは修正更新を適用済みで、更新後の稼働も確認しています。利用中の系列を保守しながら、必要な修正を取り込んでいます。PostgreSQLのバージョン方針(新しいタブで開きます)

DBの更新では利用中の拡張機能、接続するサービス、バックアップと復旧の範囲を確認しています。同じPostgreSQLの稼働単位に複数のDBがある場合は、ほかのサービスへの影響も対象です。パッケージの更新処理がDBを再起動する場合もあるため、停止するタイミングまで保守手順に含めています。

依存部品の更新と動作確認

管理画面のフレームワーク、認証SDK、画像処理ライブラリなどにも、必要な更新を適用しています。実行時に使う部品と開発用ツールを分け、間接的な依存部品も含めて、利用する機能と影響条件を照合しています。

更新時にはビルドと起動を確認し、未ログインの管理要求が拒否されることや、画像処理が動くことも確かめています。

更新した部品が本番でも使われているかを追えるよう、ロックファイル、ビルドしたソース、稼働する成果物を対応させて記録します。

検証とバックアップ・復旧手順

権限や接続認証のテストには、説明用に独立して作ったダミーの利用者・記録・DBを使っています。対象を絞った動作検証では外部通信を遮断し、本番の秘密値を入れない環境を用意しました。ソースとツールは読み取り専用にし、書き込み先を一時領域へ限定して、CPU、メモリー、プロセス数、実行時間にも上限を設けています。

本番・検証環境の確認ではURLに加えて、実際の接続先、画像の保存先、認証情報、外部通知や削除処理の対象を照合しています。環境の名前と、操作の影響が及ぶ範囲を対応させるための確認です。

対象DBの日次バックアップに加え、共有DBの更新前にもバックアップを取得しました。アーカイブ一覧の検査とSHA-256照合を完了しています。復元手順はダミーDBでバックアップを作成し、別のDBへ復元するところまで検証しました。

更新用・差戻し用パッケージも確保し、署名検証を完了しています。戻す対象ごとに、必要なものを分けています。

戻す対象 用意するものと確認すること
アプリのプログラム 以前に動いていた成果物、起動設定、切替と起動後の確認
DBの接続設定 変更前の設定、復元の手順、新しく接続できること
DBのデータ 対象時点のバックアップ、役割と設定、復元先、復元後の整合性

データを以前の時点へ戻す場合は、バックアップのあとに増えた記録や契約変更をどう保全するかも判断が必要です。プログラムの差戻しと、データの復元を区別して準備しています。

運営・保守で残す確認記録

機能の追加、部品の更新、保存先の変更があったときにも、本人の情報が本人へ届く条件を確かめ直せるよう、対象・確認日・結果・根拠を残しています。

対象 残す記録
本人とデータの対応 セッション検証、所有者の検索条件、許可・拒否のテスト
決済連携 会員へ返す項目の定義、契約との対応、通知の署名検証
非公開ファイル URLの発行権限、期限、配信時の検証、キャッシュとログの設定
DBの接続と更新 稼働中の認証規則、新規接続、バージョンと影響範囲
依存部品 ロックファイル、ビルドしたソース、稼働する成果物の対応
バックアップ 保存範囲と権限、復元を試した環境と結果

確認記録はパスワードやAPIキー、会員データを添付せずに残せます。運営者と開発・保守の担当者が、どの処理をどう確かめたか共有できることも、サービスを維持するための備えです。

会員向け機能や既存の業務システムで、データの持ち方、権限、外部サービスとの連携、更新と引継ぎを整理する支援は、サービスと製品でご案内しています。企業サイトの日常点検はWordPressの不正ログイン・不正アクセス対策にも整理しています。

参考になったら、いいねでお知らせください。