ユーザーがREST APIで自分のリソースを変更/操作することのみが許可されることを承認するための最良のソリューション


8

バックグラウンド:

現在、REST APIを構築する過程で、ノードw / expressを使用しており、モバイルアプリと最終的には(最新のブラウザーベースの)Webサイトによって消費されます。

私は、ユーザーが自分のリソースを変更することのみが許可されるように、ユーザーの更新/アクション要求を承認する最良の方法を特定しようとしています。アクションは準高頻度で発生するため、懸念事項です。

注:この使用例では、エンティティーの所有権を譲渡することはできません。

可能なソリューション:

解決策:reddisなどのサーバーセッションで各ユーザーのリソースのリストを保存および維持します。

懸念事項:サーバー側セッションの永続化には、複数のサーバーに合わせて拡張する独自の複雑さのセットがあります。これもRESTに違反しています。詳細については、do-sessions-really-violate-restfulnessをご覧ください。

解決策: ユーザーの更新/アクションクエリの前に読み取りクエリを実行します。IEは私にこのユーザーのアイテムを提供し、リストにある場合は更新を続行します。

懸念事項: ユーザーがリソースに代わって操作するたびに追加の読み取りのオーバーヘッド。

解決策: ユーザーIDをdbレイヤーに渡し、それを更新条件付きの一部にします。または、ファンシーを取得したい場合は、データバックエンドに応じて、そのリソースに対してPostgresの行レベルのセキュリティなどを使用します。

懸念事項: リソースがリクエストしているユーザーのものかどうかを確認するには、リクエストのライフサイクルの少し遅いようです。エラーは、データバックエンドからずっと上にスローされる必要があります。同じように、認証とロールベースの承認はリクエストのライフサイクルの最初に行われることが多いので、少しずれているかもしれません。実装は、データバックエンドにも依存します。また、データバックエンドにビジネスロジックを示します。

解決策: クライアント側で一種のセッションに署名しました。JWTまたは暗号化/署名されたCookieを使用します。基本的に、ユーザーのリソースIDのリストを含む信頼されたセッションを維持します。

懸念事項: クライアント側セッションのサイズ。不要な場合でも、すべてのリクエストで送信されるという事実。複数のアクティブなセッション/クライアントの可能性を導入すると、メンテナンスが非常に複雑になります。リソースが別のクライアントに追加されたときに、クライアント側の状態をどのように更新しますか。

解決策: リソースがフェッチされるときに、署名された更新トークン(JWT)またはURLをリソースとともにクライアントに渡します。リソースが更新/アクションされたときに期待します。署名されたトークンには、ユーザーIDとリソースIDが含まれ、これらに対して簡単に確認できます。

懸念事項: リソースの所有権を譲渡できる場合は複雑になりますが、私の場合は問題ありません。更新前の読み取りよりも複雑になります。ちょっと変?

最終的な考え:

私は最後の解決策に傾いていますが、それが頻繁に発生することはないので、何か不足しているのではないかと思いますか?あるいは、私が知らないデザインパターンの一部かもしれません。


1
所有権が譲渡される可能性があると仮定すると、最後のアプローチでは、そのユーザーがまだそのリソースの所有者であるかどうかを確認する必要があります。つまり、トークンに加えて他のアプローチの1つを持つことになります。
Miguel van de Laar、

@MiguelvandeLaarご返信ありがとうございます!あなたは正しいですが、私のユースケースでは、いかなる状況でも所有権を譲渡することはできません。
アシュトニアン2016年

2
「サーバーセッション」重要なものがない場合を除き、RESTはサーバー側セッションを除外します。
オービットのライトネスレース2016年

@BarryTheHatchet-それは議論の余地があるようです、それに関してここでいくつかの興味深い議論があります:stackoverflow.com/questions/6068113/…。それは新しいロードバランスサーバーの楽しみを導入作るように、その解決策も周りの最後の..です
Ashtonian

@アシュトニアン:その質問に対する131スコアの承認された回答は私にも同意します;)(223スコアのフォローアップもそうです)
オービットのライトネスレース

回答:


1

私のリストには、ユースケースに最も適した2つのソリューションがあると思います。更新前の読み取りクエリ(ソリューション2)と読み取り要求で更新トークンを送信(ソリューション5)。

パフォーマンスが心配な場合は、予想されるリソースに対する読み取りと更新の数に応じて、どちらかを決定します。読み取りよりもはるかに多くの更新が予想される場合は、リソースの所有者を特定するために追加のデータベースフェッチを実行する必要がないため、ソリューション5の方が明らかに優れています。

ただし、更新トークンを配布することによるセキュリティへの影響を忘れないでください。ソリューション2では、認証が安全であると仮定すると、サーバー上のリソースの所有者を決定するため、リソースの更新もおそらく安全です。

ソリューション5では、有効な署名を確認することを除いて、クライアントが行う主張を再確認しません。クリアテキストリンク(SSLなしなど)を介して更新を行う場合、トークン内のリソースとユーザーIDのエンコードのみでは安全ではありません。1つは、攻撃をリプレイできるようにすることです。そのため、リクエストごとに成長するナンスを含める必要があります。また、更新トークンでタイムスタンプ/有効期限をエンコードしない場合、基本的には、そのトークンを持っているすべてのユーザーに無期限の更新アクセスを付与します。最後に、ナンスが必要ない場合は、フェッチされたリソースのhmacを少なくとも含める必要があります。これにより、更新トークンは、フェッチされたときのリソースの状態に対してのみ有効になります。これにより、リプレイ攻撃がより困難になり、更新トークンの被害に関する知識がさらに制限されます。

安全なリンクを介して通信している場合でも、ナンス(または少なくともリソースの状態のhmac)と更新トークンの有効期限を追加するのは賢いことだと思います。アプリケーションドメインはわかりませんが、悪意のあるユーザーに対処すると、自分のリソースへの無制限の更新アクセスの力であらゆる種類の大混乱を引き起こす可能性があります。

nonceを追加すると、データベースのリソースごとに列が追加され、最後のフェッチ要求で送信したnonceの値が格納されます。リソースのシリアル化されたjson表現に対してhmacを計算できます。次に、メッセージ本文の一部としてではなく、追加のHTTPヘッダーとしてトークンを送信できます。

ああ、私はURLではなくトークンを使用します。RESTでは、特定のリソースの更新URLは、リソースの取得元のURLと同じでなければなりません。私はすべての認証と承認に関連するものをHTTPヘッダーに移動します。たとえば、PUTリクエストの承認ヘッダーで更新トークンを提供できます。

リソースフェッチごとに増加するノンスを追加するには、リソースフェッチリクエストごとにデータベースの更新(ノンスの新しい値)も必要になることに注意してください(ただし、リソースの状態が実際に変更されたときに、ナンスの更新のみで済む場合もあります) 、そのため、パフォーマンスの観点からは解放されます)。ただし、その情報を一時メモリに保持し、フェッチと更新要求の間にサーバーが再起動されたときにクライアントに再試行させるだけの場合は除きます。

ソリューション4の補足事項(クライアント側セッション):ユーザーが作成できるリソースの数によっては、セッションサイズは問題にならない場合があります。クライアント側のセッション更新の問題もかなり簡単に解決できる可能性があります。リソースがクライアントから受信したセッションデータにリストされていないためにリソースへの更新リクエストが失敗した場合、ユーザーは正しいのですが、そのクライアントからのセッションデータが古いかどうかをバックエンドで確認します。更新されている場合は、更新を許可し、更新要求への回答とともに更新されたCookieを送り返します。これは、ユーザーが古いローカルセッションデータでクライアントからリソースを更新しようとした場合(または悪意のあるユーザーがddosを実行しようとしたが、レート制限により救済された場合)にのみデータベースルックアップを引き起こします。しかしながら、解決策4は他の解決策よりも複雑であり、他のアイデアの1つを使用する方がよいことに同意します。ソリューション4には、考慮すべきさまざまなセキュリティの考慮事項もあります。この多くの認証状態をクライアントに保存するには、セキュリティを完全に保つ必要があります。


ユーザーが所有するすべてのリソースが子リソースとしてモデル化されている場合はどうでしょうか。(例:/ users / 1 / entity / 2)。これを、ユーザーIDを持つ署名済みJWTと組み合わせます。これを行うと、データベースにアクセスする必要がないため、認証を別のサーバー(APIゲートウェイ)に委任することもできます。
Chamindu

0

あなたが考えるかもしれない別の解決策:

作成したリソースを別のユーザーに実際に転送できない場合は、ユーザーIDをリソースIDにエンコードすることを検討してください。

たとえば、ユーザー「alice」に属するすべてのリソースは「alice-1」、「alice-2」などの名前が付けられます。

次に、「eve」が「alice-1」という名前のリソースにアクセスできるかどうかを確認するのは簡単です。

リソースの所有権を公開したくない場合は、次のように暗号化ハッシュ関数を使用できます。

  1. あなたのサーバーだけが知っているパスワードを与える
  2. そしてユーザー名u(例えば 'alice')
  3. そして、リソース名r(例えば '1')

生成hmac(password, u | '-' | r )する場所 連結です。結果をリソース名rとともにリソースのIDとして使用します。必要に応じて、salt値を追加します(パスワードの保存など)。

特定のリソースIDのユーザー 'alice'から更新要求が来た場合、リソースIDからrを抽出し、hmac(password、 'alice' | '-' | r)を計算して、リソースIDの残りの部分と比較します。 。一致する場合、アリスはリソースの更新を許可されます。一致しない場合、アリスは承認されません。また、パスワードがないと、誰がどのリソースを所有しているかがわかりません。

私はそれは安全だと思いますが、あなたはhmacsの品質をブラッシュアップしたいと思うかもしれません。


0

管理する必要がある2つの個別の問題があるようです。(1)リクエストを生成しているユーザーをどのように認証していますか?(2)そのユーザーに関連付けられているバックエンドオブジェクトをどのようにして知ることができますか。私はあなたのユースケースに適しているかもしれない観察結果も持っています(3)

(1)RESTfulな方法でのユーザー認証は決して美しくありません。私はHTTP認証/承認ヘッダーとHTTP 401応答を使用して認証をトリガーする傾向があります。つまり、すべてのリクエストでユーザーを認証する可能性があります。これはモバイルアプリケーションであるため、独自のカスタム認証スキームを作成するオプションがあります。これにより、将来のリクエストで完全な認証の詳細ではなくセッショントークンを提供できます。

(2)オブジェクトのセットに関連付けられているユーザーは、実際のオブジェクトと共にデータストアに格納する必要のある重要なデータです。ビジネスロジックの一部として、オブジェクトの所有者を確認し、オブジェクトにアクセスするIDと比較する必要があります。これは、リクエストを検証するビジネスレイヤーの一部として、コードで明示的に行います。データベースヒットが十分に大きな問題である場合、memcacheまたは(非常に大量の高可用性サイトの場合はriak)などのキー値ストアにこの情報をキャッシュし、キャッシュがコールドの場合にのみデータベースにヒットするのは理にかなっていません。このユーザー/オブジェクトの組み合わせ。

(3)ユーザーとオブジェクトの数が十分に少ない場合は、ユーザーIDとオブジェクトIDを同じフィールドに結合し、このフィールドをオブジェクトテーブルのデータベースの主キーとして使用できます。たとえば、64ビットの数値は、ユーザーIDに24ビットのスペース、オブジェクトIDに40ビットのスペースを提供します。そうすることで、オブジェクトを照会するときに、それがユーザーにとって確実であることを100%確信できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.