web-dev-qa-db-ja.com

JSON Webトークンを無効にする

私が取り組んでいる新しいnode.jsプロジェクトでは、Cookieベースのセッションアプローチからの切り替えを考えています(つまり、ユーザーのブラウザでユーザーセッションを含むKey-ValueストアにIDを保存することを意味します)。 JSON Web Tokens(jwt)を使用したトークンベースのセッションアプローチ(Key-Valueストアなし)へのアプローチ。

プロジェクトはsocket.ioを利用するゲームです - トークンベースのセッションを持つことは単一セッション(webとsocket.io)に複数の通信チャンネルがあるようなシナリオで役に立つでしょう

Jwtアプローチを使用して、サーバーからトークン/セッションの無効化をどのように提供するのですか?

また、この種のパラダイムで注目すべき一般的な(または一般的でない)落とし穴や攻撃を理解したいと思いました。たとえば、このパラダイムがセッションストア/ Cookieベースのアプローチと同じ/異なる種類の攻撃に対して脆弱であるとします。

それで、私が次のものを持っていると言う( thisthis から改作された)

セッションストアログイン:

app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        // Create session token
        var token= createSessionToken();

        // Add to a key-value database
        KeyValueStore.add({token: {userid: profile.id, expiresInMinutes: 60}});

        // The client should save this session token in a cookie
        response.json({sessionToken: token});
    });
}

トークンベースのログイン:

var jwt = require('jsonwebtoken');
app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        var token = jwt.sign(profile, 'My Super Secret', {expiresInMinutes: 60});
        response.json({token: token});
    });
}

-

セッションストアアプローチのログアウト(または無効化)には、指定されたトークンを使用してKeyValueStoreデータベースを更新する必要があります。

トークン自体には通常Key-Valueストアに存在する情報が含まれているため、このようなメカニズムはトークンベースのアプローチには存在しないようです。

303
funseiki

私もこの問題を研究してきました、そして、以下の考えのどれも完全な解決策ではありませんが、それらは他のものが考えを除外するのを助けるか、またはさらなる考えを提供するかもしれません。

1)単にクライアントからトークンを削除する

明らかにこれはサーバ側のセキュリティには何もしませんが、それは存在からトークンを削除することによって攻撃者を阻止します(すなわち彼らはログアウトの前にトークンを盗まれなければならないでしょう)。

2)トークンブラックリストを作成する

無効なトークンを最初の有効期限まで保存して、受信したリクエストと比較することができます。ただし、リクエストごとにデータベースにアクセスする必要があるため、これは最初の段階で完全にトークンベースになる理由を否定するようです。ただし、ログアウトと有効期限の間にあるトークンを格納するだけでよいため、ストレージサイズはもっと小さくなる可能性があります(これは直感であり、間違いなくコンテキストに依存します)。

3)トークンの有効期限を短くして頻繁に入れ替える

トークンの有効期限を十分な間隔で保ち、実行中のクライアントに必要に応じて更新を追跡して要求させると、1番が完全なログアウトシステムとして効果的に機能します。この方法の問題点は、クライアントのコードが閉じられるまでユーザーをログインさせ続けることが不可能になることです(有効期限をどれくらい長くするかによって異なります)。

危機管理計画

緊急事態が発生した場合、またはユーザートークンが危険にさらされた場合は、ユーザーが自分のログイン資格情報を使用して基になるユーザー検索IDを変更できるようにすることができます。関連付けられているユーザーが見つからなくなるため、これにより、関連付けられているすべてのトークンが無効になります。

また、最後のログイン日をトークンに含めることをお勧めします。そうすれば、しばらくしても再ログインを強制できるようになります。

トークンを使用した攻撃に関する類似点/相違点については、この投稿で質問に答えます。 http://blog.auth0.com/2014/01/07/angularjs-authentication-with-cookies-vs-token/

272
Matt Way

上記のアイデアは良いですが、既存のJWTをすべて無効にするための非常に単純で簡単な方法は、単に秘密を変更することです。

サーバーがJWTを作成し、それにシークレット(JWS)を付けてそれをクライアントに送信すると、単にシークレットを変更すると、既存のすべてのトークンが無効になり、サーバーに。

実際のトークンの内容(または検索ID)を変更する必要はありません。

明らかにこれはあなたがすべての既存のトークンを期限切れにしたいときに緊急の場合にのみうまくいきます。

70
Andy

これは主に長いコメントで、@ mattwayによる回答を支持し、その上に構築されています。

与えられた:

このページで提案されている他の解決策の中には、リクエストごとにデータストアをヒットすることを推奨するものがあります。すべての認証要求を検証するためにメインデータストアにアクセスした場合は、他の確立されたトークン認証メカニズムの代わりにJWTを使用する理由がわかりません。毎回データストアにアクセスする場合は、ステートレスではなく、JWTを本質的にステートフルにしました。

(あなたのサイトが大量の不正なリクエストを受信した場合、JWTはデータストアにアクセスせずにそれらを拒否します。これは役に立ちます。おそらくそのような他のユースケースがあります。)

与えられた:

ステートレスJWTには、次のような重要なユースケースに 即時 および 安全な サポートを提供する方法がないため、真のステートレスJWT認証は、一般的な現実のWebアプリケーションでは実現できません。

ユーザーのアカウントが削除/ブロック/一時停止されています。

ユーザーのパスワードが変更されました。

ユーザーの役割または権限が変更されました。

ユーザーは管理者によってログアウトされています。

JWTトークン内の他のアプリケーションの重要データは、サイト管理者によって変更されます。

このような場合、トークンの有効期限を待つことはできません。トークンの無効化はただちに発生する必要があります。また、悪意を持っていてもいなくても、古いトークンのコピーを保持して使用しないようにクライアントを信頼することはできません。

そのため、@ matt-wayからの回答、#2 TokenBlackListが、JWTベースの認証に必要な状態を追加する最も効率的な方法になると思います。

有効期限が切れるまでこれらのトークンを保持するブラックリストがあります。有効期限が切れるまでブラックリストに登録されたトークンを保存するだけでよいため、トークンのリストはユーザーの総数に比べてかなり少なくなります。無効化されたトークンをredis、memcached、またはキーの有効期限の設定をサポートする他のメモリ内データストアに配置することで実装します。

最初のJWT認証を通過するすべての認証要求に対して、メモリ内のdbを呼び出す必要がありますが、そこにユーザーのセット全体のキーを格納する必要はありません。 (これは特定のサイトにとって大したことではないかもしれません。)

53
Ed J

私はjwtのバージョン番号をユーザーモデルに記録しておくでしょう。新しいjwtトークンはそれらのバージョンをこれに設定します。

Jwtを検証するときには、ユーザーの現在のjwtバージョンと同じバージョン番号であることを確認してください。

古いjwtsを無効にしたいときはいつでも、単にユーザjwtのバージョン番号を上げてください。

36
DaftMonk

まだ試していませんが、他のいくつかの回答に基づいて多くの情報を使用しています。ここでの複雑さは、ユーザー情報の要求ごとにサーバー側のデータストア呼び出しを回避することです。他のほとんどのソリューションでは、ユーザーセッションストアへの要求ごとにdbルックアップが必要です。特定のシナリオではこれで問題ありませんが、これはそのような呼び出しを回避し、必要なサーバー側の状態を非常に小さくするために作成されました。 サーバー側のセッションを再作成することになりますが、すべての強制無効化機能を提供するには小さすぎます。あなたがそれをしたいのであればここで要旨です:

目標:

  • データストアの使用を軽減します(ステートレス)。
  • すべてのユーザーを強制的にログアウトさせる機能。
  • いつでも任意の個人を強制的にログアウトさせる機能。
  • 一定時間後にパスワードの再入力を要求する機能。
  • 複数のクライアントと連携する機能。
  • ユーザーが特定のクライアントからログアウトをクリックしたときに再ログインを強制する機能。 (ユーザーが立ち去った後に誰かがクライアントトークンを「削除しない」のを防ぐため - 追加情報についてはコメントを参照)

解決策:

  • refresh-token 保存されている寿命の長い(数時間)クライアントとペアになった、寿命の短い(500万未満)アクセストークンを使用します。
  • すべてのリクエストは、認証またはリフレッシュトークンの有効期限の有効性を確認します。
  • アクセストークンの有効期限が切れると、クライアントは更新トークンを使用してアクセストークンを更新します。
  • 更新トークンの確認中に、サーバーはユーザーIDの小さなブラックリストを確認します(見つかった場合は更新要求を拒否します)。
  • クライアントに有効な(期限切れではない)更新トークンまたは認証トークンがない場合、他のすべての要求は拒否されるので、ユーザーは再度ログインする必要があります。
  • ログイン要求時に、禁止のためにユーザデータストアをチェックしてください。
  • ログアウト時 - セッションブラックリストにそのユーザーを追加して再度ログインする必要があります。マルチデバイス環境ですべてのデバイスからログアウトしないようにするには追加情報を保存する必要があります。ユーザーブラックリスト。
  • X時間後に再入力を強制するには - 認証トークンの最後のログイン日を維持し、要求ごとにそれを確認します。
  • すべてのユーザーを強制的にログアウトさせるには - トークンハッシュキーをリセットします。

これには、ユーザーテーブルに禁止されたユーザー情報が含まれていると仮定して、サーバー上でブラックリスト(状態)を管理する必要があります。無効なセッションブラックリスト - ユーザーIDのリストです。このブラックリストは、リフレッシュトークン要求中にのみチェックされます。更新トークンTTLである限り、エントリーはその上に存在する必要があります。更新トークンが期限切れになると、ユーザーはログインし直す必要があります。

短所:

  • リフレッシュトークン要求でデータストアルックアップを実行する必要があります。
  • 無効なトークンは、アクセストークンのTTLに対して機能し続ける可能性があります。

長所:

  • 望ましい機能を提供します。
  • 通常の操作では、トークンの更新アクションはユーザーには表示されません。
  • リクエストごとではなく、リフレッシュリクエストでデータストアルックアップを実行するためにのみ必要です。すなわち、毎秒1回ではなく15分ごとに1回です。
  • サーバー側の状態を非常に小さいブラックリストに最小化します。

このソリューションでは、Reddisのようなインメモリデータストアは必要ありません。少なくとも15分ごとにサーバーがdb呼び出しを行うだけなので、ユーザー情報は不要です。 reddisを使用している場合、そこに有効/無効なセッションリストを保存することは非常に速くて簡単な解決策でしょう。更新トークンは必要ありません。各認証トークンはセッションIDとデバイスIDを持ち、作成時にはReddisテーブルに格納され、適切な場合は無効化されます。それからそれらはすべてのリクエストでチェックされ、無効なときは拒否されます。

25
Ashtonian

私が検討してきたアプローチは、JWTで常にiat(issu at)値を持つことです。その後、ユーザーがログアウトしたら、そのタイムスタンプをユーザーレコードに保存します。 JWTを検証するときは、iatを最後にログアウトしたタイムスタンプと比較するだけです。 iatが古ければ、それは無効です。はい、あなたはDBに行かなければなりませんが、JWTが有効であれば私はとにかくユーザーレコードを引っ張ります。

私の考えている主な欠点は、複数のブラウザを使用している場合、またはモバイルクライアントを使用している場合は、すべてのセッションからログアウトすることです。

これは、システム内のすべてのJWTを無効にするための優れたメカニズムにもなります。チェックの一部は、最後の有効なiat時刻のグローバルタイムスタンプに対するものかもしれません。

13
Brack Mo

私はここで少し遅れていますが、私はまともな解決策を持っていると思います。

データベースに "last_password_change"列があり、パスワードが最後に変更された日時が格納されています。発行日時もJWTに格納しています。トークンを検証するときは、トークンが発行された後にパスワードが変更されていて、まだ期限切れになっていなくてもトークンが拒否されたかどうかを確認します。

8
Matas Kairaitis

あなたのDBの "last_key_used"フィールドをあなたのユーザーの文書/レコードに置くことができます。

ユーザーがuserでログインしてパスしたら、新しいランダムな文字列を生成し、それをlast_key_usedフィールドに格納し、トークンに署名するときにペイロードに追加します。

ユーザーがトークンを使用してログインしたら、トークン内のものと一致するようにDB内のlast_key_usedを確認します。

次に、ユーザーがたとえばログアウトをしたとき、またはトークンを無効にしたい場合は、その "last_key_used"フィールドを別のランダムな値に変更するだけで、以降のチェックはすべて失敗します。

5
NickVarcha

パーティーに遅れて、私の2セントはいくつかの研究の後に下に与えられています。ログアウト中に、次のことが起こっていることを確認してください。

クライアントストレージ/セッションをクリアする

ログインまたはログアウトが発生するたびに、ユーザーテーブルの最終ログイン日時およびログアウト日時を更新します。そのため、ログイン日時は常にログアウト日時よりも長くする必要があります(または、現在のステータスがloginで、まだログアウトされていない場合は、ログアウト日時をnullにしてください)

これは、ブラックリストの追加表を保持して定期的にパージするよりもはるかに簡単です。複数のデバイスをサポートするには、OSまたはクライアントの詳細など、ログアウト日、ログアウト日を保存するための追加のテーブルが必要です。

2
Shamseer
  1. トークンの有効期限は1日です。
  2. 毎日のブラックリストを維持しなさい。
  3. 無効化/ログアウトトークンをブラックリストに入れる

トークンを検証するには、まずトークンの有効期限をチェックし、次にトークンが期限切れでない場合はブラックリストをチェックします。

セッションのニーズが長い場合は、トークンの有効期限を延長するためのメカニズムが必要です。

2
Ebru Yener

このようなメモリ内のリストを保管してください

user_id   revoke_tokens_issued_before
-------------------------------------
123       2018-07-02T15:55:33
567       2018-07-01T12:34:21

トークンが1週間で期限切れになる場合は、それより古いレコードを消去するか無視してください。また、各ユーザーの最新の記録のみを保管してください。リストのサイズは、トークンを保持する期間と、ユーザーが自分のトークンを取り消す頻度によって異なります。テーブルが変更された場合にのみdbを使用してください。アプリケーションの起動時にメモリにテーブルをロードします。

2
Eduardo

「すべてのデバイスからログアウトする」オプションが許容される場合(ほとんどの場合それはそれです):

  • トークンレコードフィールドをユーザーレコードに追加します。
  • このフィールドの値をJWTに保管されている請求に追加してください。
  • ユーザーがログアウトするたびにバージョンを上げます。
  • トークンを検証するときに、そのバージョンクレームをユーザーレコードに格納されているバージョンと比較し、同じでない場合は拒否します。

いずれにしても、ほとんどの場合、ユーザーレコードを取得するためのdbトリップが必要になるので、これによって検証プロセスに大きなオーバーヘッドがかかることはありません。ブラックリストを維持するのとは異なり、結合や別の呼び出しを使用したり、古いレコードを削除したりする必要があるため、DBの負荷が大きくなります。

1
user2555515

ユーザートークンを無効にしたい場合は、DB上で発行されたすべてのトークンを追跡し、それらがセッションのようなテーブルで有効かどうかを確認できます。欠点は、リクエストごとにDBにアクセスすることです。

試したことはありませんが、DBのヒットを最小限に抑えながらトークンの失効を許可するには、次の方法をお勧めします -

データベースチェック率を下げるために、いくつかの決定論的な関連付けに従って、発行されたすべてのJWTトークンをX個のグループに分割します(たとえば、ユーザーIDの最初の数字で10個のグループ)。

各JWTトークンは、グループ作成時に作成されたグループIDとタイムスタンプを保持します。例:{ "group_id": 1, "timestamp": 1551861473716 }

サーバーはすべてのグループIDをメモリに保持し、各グループにはそのグループに属するユーザーの最後のログアウトイベントがいつ発生したかを示すタイムスタンプが付けられます。例:{ "group1": 1551861473714, "group2": 1551861487293, ... }

古いグループタイムスタンプを持つJWTトークンを持つ要求は、有効性(DBヒット)についてチェックされ、有効な場合は、クライアントが将来使用するために新しいタイムスタンプを持つ新しいJWTトークンが発行されます。トークンのグループタイムスタンプが新しい場合は、JWTを信頼します(DBヒットなし)。

そう -

  1. トークンに古いグループのタイムスタンプがある場合にのみ、DBを使用してJWTトークンを検証しますが、将来の要求はユーザーのグループの誰かがログアウトするまで検証されません。
  2. タイムスタンプの変更数を制限するためにグループを使用します(明日がないようにログインとログアウトをするユーザーがいるとします - 全員ではなく限られた数のユーザーにのみ影響します)。
  3. メモリに保持されるタイムスタンプの量を制限するためにグループの数を制限します
  4. トークンを無効にするのは簡単です。セッションテーブルからトークンを削除し、ユーザーのグループの新しいタイムスタンプを生成するだけです。
1
Arik

単にjtiクレーム(nonce)を使用し、それをユーザレコードフィールドとしてリストに格納してはいけません(dbに依存しますが、少なくともカンマ区切りのリストで問題ありません)。他のユーザがおそらくユーザレコードを取得したいと指摘しているので、個別に検索する必要はありません。これにより、さまざまなクライアントインスタンスに対して複数の有効なトークンを設定できます( "logout everywhere"でリストを空にリセットできます)。

1
davidkomer

私はそれを次のようにしました:

  1. unique hashを生成し、それを redis とあなたの _ jwt _ に保存します。これは session と呼ぶことができます。
    • リクエスト 特定の _ jwt _ の数も格納します - jwtがサーバーに送信されるたびに、 リクエスト integerの値を増やします。 。 (これはオプションです)

そのため、ユーザーがログインすると、一意のハッシュが作成され、redisに格納されて _ jwt _ に挿入されます。

ユーザーが保護されたエンドポイントにアクセスしようとすると、 _ jwt _ から一意のセッションハッシュを取得し、redisを照会してそれが一致するかどうかを確認します。

これを拡張して、 _ jwt _ をさらに安全にすることができます。

_ x _ が特定の _ jwt _ を要求するたびに、新しい一意のセッションを生成し、それを _ jwt _ に格納してから、ブラックリストに追加します。前回のもの。

これは、 _ jwt _ が絶えず変化し、古くなっている _ jwt _ がハッキングされたり、盗まれたり、その他のことを阻止されたことを意味します。

1
James111

ユーザーごとに一意の文字列、およびグローバル文字列をまとめたもの

0
Mark Essel