web-dev-qa-db-ja.com

「オフラインの場合がある」Webアプリで使用する一意で安全な識別子を生成するための戦略

ユーザーがオンラインとオフラインの両方で作業できるWebベースのプロジェクトがあり、クライアント側でレコードの一意のIDを生成する方法を探しています。ユーザーがオフラインである(つまり、サーバーと通信できない)ときに機能し、一意であることが保証され、安全なアプローチが必要です。 「安全」とは、クライアントが重複してIDを(悪意を持ってまたは他の方法で)送信し、それによってデータの整合性に大混乱をもたらすことを特に心配することです。

私はこれがすでに解決された問題であることを望んで、グーグルをやっています。特に本番システムで使用されているアプローチに関して、非常に明確なものは見つかりませんでした。ユーザーが自分で作成したデータにのみアクセスするシステムの例をいくつか見つけました(たとえば、複数のデバイスでアクセスされるが、それを作成したユーザーのみがアクセスするTodoリスト)。残念ながら、もう少し洗練されたものが必要です。私はいくつかの本当に良いアイデアを見つけました ここ 、それは私が物事がうまくいくかもしれないと思っていた方法と一致しています。

以下は私の提案する解決策です。

いくつかの要件

  1. IDはグローバルに一意である必要があります(またはシステム内で少なくとも一意である必要があります)
  2. クライアントで生成されます(つまり、ブラウザーのJavaScriptを介して)
  3. セキュア(上記およびその他の方法で概説)
  4. データは、それを作成しなかったユーザーを含む複数のユーザーが表示/編集できます
  5. バックエンドデータベース(MongoDBやCouchDBなど)に重大なパフォーマンスの問題を引き起こしません

提案されたソリューション

ユーザーがアカウントを作成すると、サーバーによって生成され、システム内で一意であることがわかっているuuidがユーザーに与えられます。このIDは、ユーザー認証トークンと同じであってはなりません。このIDをユーザーの「IDトークン」と呼びましょう。

ユーザーが新しいレコードを作成すると、JavaScriptで新しいUUIDが生成されます(可能な場合はwindow.cryptoを使用して生成されます。例を参照してください here )。このIDは、ユーザーがアカウントを作成したときに受け取った「IDトークン」と連結されます。この新しい複合ID(サーバー側のIDトークン+クライアント側のUUID)は、レコードの一意の識別子になりました。ユーザーがオンラインで、この新しいレコードをバックエンドサーバーに送信すると、サーバーは次のことを行います。

  1. これを「挿入」アクションとして識別します(つまり、更新または削除ではありません)。
  2. 複合キーの両方の部分が有効なUUIDであることを確認します
  3. 提供された複合IDの「idトークン」部分が現在のユーザーに対して正しいことを検証します(つまり、サーバーがユーザーのアカウントを作成したときにユーザーに割り当てられたIDトークンと一致します)。
  4. すべてがcopaseticの場合は、データをデータベースに挿入します(「upsert」ではなく挿入を行うように注意してください。これにより、iddoesがすでに存在する場合は、誤って既存のレコードを更新することはありません)

クエリ、更新、削除には、特別なロジックは必要ありません。従来のアプリケーションと同じ方法で、レコードのIDを使用するだけです。

このアプローチの利点は何ですか?

  1. クライアントコードはオフラインで新しいデータを作成し、そのレコードのIDをすぐに知ることができます。一時的なIDがクライアントで生成され、後でシステムがオンラインになったときに「最終的な」IDに交換される別のアプローチを検討しました。しかし、これは非常にもろく感じられました。特に、更新する必要がある外部キーを使用して子データを作成することを考え始めた場合は特にそうです。 IDが変更されたときに変更されるURLの扱いについては言うまでもありません。

  2. Idをクライアントが生成した値とサーバーが生成した値の複合にすることにより、各ユーザーは効果的にサンドボックスにIDを作成します。これは、悪意のある/悪意のあるクライアントによる被害を制限することを目的としています。また、IDの衝突はユーザーごとに発生し、システム全体にグローバルではありません。

  3. ユーザーIDトークンはアカウントに関連付けられているため、IDは、認証されたクライアント(つまり、ユーザーが正常にログインした場所)がユーザーのサンドボックスでのみ生成できます。これは、悪意のあるクライアントがユーザーに不正なIDを作成しないようにすることを目的としています。もちろん、ユーザー認証トークンが悪意のあるクライアントによって盗まれた場合、悪意のある行為を行う可能性があります。ただし、認証トークンが盗まれると、アカウントはとにかく侵害されます。これが発生した場合、行われる被害は、システム全体ではなく、侵害されたアカウントに限定されます。

懸念

このアプローチに関する私の懸念のいくつかはここにあります

  1. これにより、大規模なアプリケーションに対して十分に一意のIDが生成されますか?これがidの衝突を引き起こすと考える理由はありますか?これが機能するために、JavaScriptは十分にランダムなUUIDを生成できますか? window.cryptoは かなり広く利用可能 のように見え、このプロジェクトはすでにかなり新しいブラウザを必要としています。 ( この質問には、別のSO独自の質問 があります)

  2. 悪意のあるユーザーがシステムを危険にさらす可能性のある抜け穴はありますか?

  3. 2つのuuidで構成される複合キーを照会するときに、DBのパフォーマンスを心配する理由はありますか?最高のパフォーマンスを得るには、このIDをどのように保存する必要がありますか? 2つの別個のフィールドまたは単一のオブジェクトフィールド? MongoとCouchには別の「最良の」アプローチがありますか?非順次の主キーがあると、挿入を実行するときに顕著なパフォーマンスの問題が発生する可能性があることを知っています。自動生成された主キーの値を持ち、このIDを別のフィールドとして保存する方が賢明でしょうか? ( この質問には、別のSO独自の質問 があります)

  4. この戦略を使用すると、特定のレコードのセットが同じユーザーによって作成されたと簡単に判断できます(それらはすべて同じ公開されているIDトークンを共有するため)。私にはこれに関する差し迫った問題はありませんが、必要以上に内部の詳細に関する情報を漏らさない方が常に良いです。もう1つの可能性は、複合キーをハッシュすることですが、それは価値があるよりも問題が多いようです。

  5. ユーザーのIDの衝突が発生した場合、回復する簡単な方法はありません。クライアントが新しいIDを生成する可能性があると思いますが、これはEdgeケースでは実際には発生しないはずの多くの作業のようです。私はこれに対処しないでおくつもりです。

  6. 認証されたユーザーのみがデータを表示または編集できます。これは私のシステムにとって許容できる制限です。

結論

合理的な計画を上回っていますか?これの一部は、問題のアプリケーションの完全な理解に基づく判断の呼びかけに帰着することを理解しています。

47
herbrandson

あなたのアプローチはうまくいきます。多くのドキュメント管理システムがこのタイプのアプローチを使用しています。

考慮すべきことの1つは、ユーザーuuidとランダムなアイテムIDの両方を文字列の一部として使用する必要がないことです。代わりに、両方の連結をハッシュできます。これにより、識別子が短くなり、結果として得られるIDがより均等に分散されるため、他のいくつかの利点が得られます(インデックス作成、およびUUIDに基づいてファイルを保存している場合はファイルストレージのバランスが向上します)。

あなたが持っている別のオプションは、各アイテムの一時的なuuidを生成することです。次に、サーバーに接続して投稿すると、サーバーは各アイテムの(保証された)uuidを生成し、それをユーザーに返します。次に、ローカルコピーを更新します。

4
GrandmasterB

2つの懸念事項を分離する必要があります。

  1. ID生成:クライアントは、分散システムで一意の識別子を生成できる必要があります
  2. セキュリティ上の懸念:クライアントには有効なユーザー認証トークンが必要であり、認証トークンは作成または変更されるオブジェクトに対して有効です

残念ながら、これら2つの解決策は別々です。しかし幸いなことに、それらには互換性がありません。

IDの生成に関する問題は、UUIDを使用して生成することで簡単に解決できます。ただし、セキュリティ上の懸念があるため、指定された認証トークンが操作に対して承認されていることをサーバーで確認する必要があります(つまり、認証トークンがその特定のオブジェクトに必要な権限を所有していないユーザー用である場合は、拒否されました)。

衝突が正しく処理された場合、実際にはセキュリティ上の問題は発生しません(ユーザーまたはクライアントは、別のUUIDで操作を再試行するように求められるだけです)。

12
Lie Ryan