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


47

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

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

以下は私の提案するソリューションです。

いくつかの要件

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

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

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

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

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

クエリ、更新、削除には特別なロジックは必要ありません。従来のアプリケーションと同じ方法で、レコードの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トークンを共有するため)。これに関する差し迫った問題はありませんが、必要以上に内部の詳細についての情報を漏らさない方が良いです。別の可能性は、複合キーをハッシュすることですが、それはそれが価値があるよりももっと面倒かもしれません。

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

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

結論

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


私はこの質問はあなたがinteressかもしれないと思うstackoverflow.com/questions/105034/... GUIDのように私にも、このリードが、彼らはJavaScriptでネイティブのように見えるしていない
レミ

2
UUIDがリストされている5つの要件をすでに満たしていることは印象的です。なぜ不十分なのですか?
ゲイブ14

嘘のryansの私のコメントを参照してください@Gabeの下に掲示
herbrandson

:この質問のメタ議論meta.stackoverflow.com/questions/251215/...
ブヨ

「悪意のある/不正なクライアント」-不正。
デビッドコンラッド14

回答:


4

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

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

もう1つのオプションは、各アイテムに対して一時的なuuidを生成することです。次に、それらを接続してサーバーに送信すると、サーバーは各アイテムの(保証された)uuidを生成し、それを返します。次に、ローカルコピーを更新します。


2
IDとして2のハッシュを使用することを検討していました。ただし、サポートする必要のあるすべてのブラウザーでsha256を生成する適切な方法があるとは思えませんでした。(
herbrandson 14

12

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

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

残念ながら、これら2つのソリューションは別々です。しかし、幸いにも互換性はありません。

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

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


これは本当に良い点です。おそらくそれが必要なすべてであり、私はそれを考え直しています。しかし、このアプローチにはいくつかの懸念があります。最大の理由は、javascriptで生成されたuuidが希望するほどランダムではないように見えることです(詳細については、stackoverflow.com / a / 2117523/13181のコメントを参照してください)。window.cryptoでこの問題を解決できるようですが、サポートする必要があるすべてのブラウザーバージョンで利用できるとは限りません。
herbrandson

つづき...衝突の場合に新しいuuidを再生成するコードをクライアントに追加するという提案が好きです。ただし、これにより、「このアプローチの利点は何ですか」セクションのポイント1の下で、私が投稿で抱えていた懸念が再導入されるように思われます。私はそのルートを行けば、私はより良いオフだけの一時的なIDのクライアント側を生成してから接続したら、サーバーから「最後のID」でそれらを更新するだろうと思います
herbrandson

再び続けました...さらに、ユーザーが独自の一意のIDを送信できるようにすることは、セキュリティ上の懸念のように思われます。おそらく、UUIDのサイズと衝突の統計的に高い可能性は、それ自体でこの懸念を緩和するのに十分です。よく分かりません。この場合、各ユーザーを独自の「サンドボックス」に保持することは良い考えであるという疑わしい疑念があります(つまり、ユーザー入力を信頼しない)。
herbrandson

@herbrandson:ユーザーが操作に対するアクセス許可を持っていることを常に確認する限り、ユーザーが独自の一意のIDを生成できるようにすることで考えられるセキュリティ問題はありません。IDはオブジェクトを識別するために使用できるものであり、その値が何であるかは実際には関係ありません。唯一の潜在的な害は、ユーザーが自分の使用のためにIDの範囲を予約できることですが、他のユーザーが偶然だけでそれらの値に到達する可能性はほとんどないため、システム全体に問題を引き起こすことはありません。
ライライアン14

ご意見をいただきありがとうございます。それは本当に私の思考を明確にすることを余儀なくされました!私はあなたのアプローチに警戒した理由があり、途中でそれを忘れていました:)。私の恐怖は、多くのブラウザーの貧弱なRNGに結びついています。uuid生成には、暗号的に強力なRNGを好むでしょう。多くの新しいブラウザーはwindow.cryptoを介してこれを持っていますが、古いブラウザーは持っていません。このため、悪意のあるユーザーが別のユーザーRNGのシードを把握し、生成される次のuuidを知る可能性があります。これは、攻撃される可能性があると感じる部分です。
ハーブランドソン14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.