2
「時々オフライン」のWebアプリで使用するための一意で安全な識別子を生成するための戦略
ユーザーがオンラインとオフラインの両方で作業できるWebベースのプロジェクトがあり、クライアント側でレコードの一意のIDを生成する方法を探しています。ユーザーがオフライン(つまり、サーバーと通信できない)で機能し、一意であることが保証され、安全なアプローチが必要です。「安全」ということで、クライアントが重複したIDを(悪意のあるかどうかに関係なく)送信し、それによってデータの整合性が破壊されることを特に心配しています。 これがすでに解決された問題であることを願って、私はいくつかのグーグルをしてきました。特に本番システムで使用されているアプローチに関して、非常に決定的なものは見つかりませんでした。ユーザーが作成したデータのみにアクセスするシステムの例をいくつか見つけました(たとえば、複数のデバイスでアクセスするTodoリストですが、作成したユーザーのみがアクセスできます)。残念ながら、もう少し洗練されたものが必要です。私はここでいくつかの本当に良いアイデアを見つけましたが、それは私が物事がうまくいくと思っていた方法と一致しています。 以下は私の提案するソリューションです。 いくつかの要件 IDはグローバルに一意(または、システム内で少なくとも一意)である必要があります クライアント上で生成されます(つまり、ブラウザーのJavaScriptを介して) セキュア(上記およびその他の概要に従って) データは、作成していないユーザーを含む複数のユーザーが表示/編集できます。 バックエンドデータベース(MongoDBやCouchDBなど)に重大なパフォーマンスの問題を引き起こさない 提案されたソリューション ユーザーがアカウントを作成すると、サーバーによって生成され、システム内で一意であることがわかっているuuidが与えられます。このIDは、ユーザー認証トークンと同じであってはなりません。このIDをユーザーの「IDトークン」と呼びましょう。 ユーザーが新しいレコードを作成すると、javascriptに新しいuuidが生成されます(window.cryptoを使用して生成されます(使用可能な場合はこちらを参照)。このIDは、ユーザーがアカウントを作成したときに受け取った「IDトークン」と連結されます。この新しい複合ID(サーバー側IDトークン+クライアント側UUID)は、レコードの一意の識別子になりました。ユーザーがオンラインで、この新しいレコードをバックエンドサーバーに送信すると、サーバーは次のことを行います。 これを「挿入」アクション(更新や削除ではない)として識別します 複合キーの両方の部分が有効なuuidであることを検証します 複合IDの指定された「IDトークン」部分が現在のユーザーに対して正しいことを検証します(つまり、アカウントを作成したときにサーバーがユーザーに割り当てたIDトークンと一致します) すべてがcopaseticである場合、dbにデータを挿入します(id が既に存在する場合、誤って既存のレコードを更新しないように、「upsert」ではなく挿入を行うように注意してください) クエリ、更新、削除には特別なロジックは必要ありません。従来のアプリケーションと同じ方法で、レコードのIDを使用するだけです。 このアプローチの利点は何ですか? クライアントコードはオフラインで新しいデータを作成し、そのレコードのIDをすぐに知ることができます。クライアント上で一時IDが生成され、システムがオンラインのときに「最終」IDに交換される代替アプローチを検討しました。しかし、これは非常に脆い感じがしました。特に、更新が必要な外部キーを使用して子データを作成することを検討し始めたとき。IDが変更されたときに変更されるURLの処理は言うまでもありません。 IDをクライアント生成値とサーバー生成値の複合にすることにより、各ユーザーはサンドボックスにIDを効率的に作成します。これは、悪意のある/悪意のあるクライアントが行うことができる損害を制限することを目的としています。また、idの衝突はユーザーごとに発生し、システム全体にグローバルではありません。 ユーザーIDトークンはアカウントに関連付けられているため、IDは、認証されたクライアント(つまり、ユーザーが正常にログインした場所)によってのみユーザーサンドボックスで生成できます。これは、悪意のあるクライアントがユーザーの不正なIDを作成しないようにすることを目的としています。もちろん、ユーザー認証トークンが悪意のあるクライアントに盗まれた場合、悪いことをする可能性があります。しかし、認証トークンが盗まれると、アカウントはとにかく危険にさらされます。これが発生した場合、被害はシステム全体ではなく、侵害されたアカウントに限定されます。 懸念事項 このアプローチに関する私の懸念のいくつかを以下に示します これにより、大規模なアプリケーションに対して十分に一意のIDが生成されますか?これがIDの衝突を引き起こすと考える理由はありますか?javascriptはこれが機能するために十分にランダムなUUIDを生成できますか?window.cryptoはかなり広く利用可能であり、このプロジェクトはすでに合理的な最新のブラウザを必要としているようです。(この質問には、独自のSO質問があります) 悪意のあるユーザーがシステムを危険にさらす可能性のある抜け穴がありますか? 2つのuuidで構成される複合キーを照会するときに、DBのパフォーマンスを心配する理由はありますか。最高のパフォーマンスを得るには、このIDをどのように保存する必要がありますか?2つの別々のフィールドまたは単一のオブジェクトフィールド?Mongo対Couchに異なる「最良の」アプローチはありますか?連続していない主キーがあると、挿入時に顕著なパフォーマンスの問題が発生することがあります。主キーに自動生成された値を持ち、このIDを別のフィールドとして保存する方が賢明でしょうか?(この質問には、独自のSO質問があります) この戦略を使用すると、特定のレコードセットが同じユーザーによって作成されたことを簡単に判断できます(すべてのユーザーが同じ公開IDトークンを共有するため)。これに関する差し迫った問題はありませんが、必要以上に内部の詳細についての情報を漏らさない方が良いです。別の可能性は、複合キーをハッシュすることですが、それはそれが価値があるよりももっと面倒かもしれません。 ユーザーのID衝突が発生した場合、回復する簡単な方法はありません。クライアントは新しいIDを生成できたと思いますが、これは実際には発生しないはずのエッジケースでは多くの作業のようです。私はこれを未解決のままにするつもりです。 認証されたユーザーのみがデータを表示および/または編集できます。これは私のシステムにとって許容できる制限です。 結論 合理的な計画を超えていますか?この一部は、問題のアプリケーションの完全な理解に基づいた判断の呼び出しに帰着することを理解しています。