JSON Webトークンの無効化


421

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

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

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

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

だから、私は次のものを持っていると言います(これとこれから適応されます):

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

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データベースを更新する必要があります。

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


1
'express-jwt'パッケージを使用している場合は、isRevokedオプションを確認するか、同じ機能を複製してみることができます。github.com/auth0/express-jwt#revoked-tokens
Signus

1
アクセストークンに短い有効期限を使用し、有効期限の長い更新トークンを使用して、データベース内のユーザーのアクセスステータスをチェックできるようにします(ブラックリスト)。auth0.com/blog/…–
Rohmer

別のオプションは、jwtトークンを生成し、同じIPアドレスの格納されたIPと着信要求を確認しながら、ペイロードにIPアドレスをアタッチすることです。例:nodeJsのreq.connection.remoteAddress。顧客ごとに静的IPを発行しないISPプロバイダーがありますが、クライアントがインターネットに再接続しない限り、これは問題にならないと思います。
Gihan Sandaru

回答:


392

私もこの質問を調査しており、以下のアイデアは完全な解決策ではありませんが、それらは他の人がアイデアを除外したり、さらに提案するのに役立つ可能性があります。

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

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

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

無効なトークンを最初の有効期限まで保存し、着信リクエストと比較することができます。ただし、すべてのリクエストでデータベースにアクセスする必要があるため、そもそも完全にトークンベースになる理由は否定されているようです。ただし、ログアウトと有効期限の間のトークンのみを保存する必要があるため、ストレージサイズはおそらく小さくなります(これは直感であり、コンテキストに明らかに依存しています)。

3)トークンの有効期限を短くし、頻繁にローテーションする

トークンの有効期限を十分に短い間隔で維持し、実行中のクライアントに追跡を継続させ、必要に応じて更新を要求する場合、1は完全なログアウトシステムとして効果的に機能します。この方法の問題は、クライアントコードを閉じるまでの間にユーザーをログインしたままにしておくことができないことです(有効期限の間隔によって異なります)。

緊急時対応計画

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

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

トークンを使用した攻撃に関する類似点/相違点に関して、この投稿は次の質問を扱います:https : //github.com/dentarg/blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies -vs-token.markdown


3
優れたアプローチ。私の直感は、3つすべての組み合わせを実行すること、および/または(タイマーではなく)すべての "n"要求の後に新しいトークンを要求することです。メモリ内のオブジェクトストレージにredisを使用しており、これをケース#2に簡単に使用すると、レイテンシが大幅に減少します。
アーロンワーグナー

2
このコーディングのホラー投稿はいくつかのアドバイスを提供します:セッションベアリングCookie(またはトークン)は短く保ちますが、ユーザーには見えないようにします-これは#3と一致しているようです。私自身の直感(おそらく、より伝統的であるため)は、トークン(またはそのハッシュ)をホワイトリストのセッションデータベース(#2と同様)へのキーとして機能させるだけです
funseiki

8
記事はよく書かれていて、2)上記の精巧なバージョンです。正常に動作しますが、個人的には従来のセッションストアとの違いはあまりありません。ストレージ要件は低くなると思いますが、それでもデータベースが必要です。私にとってJWTの最大の魅力は、セッションにデータベースをまったく使用しないことでした。
Matt Way、

211
ユーザーがパスワードを変更したときにトークンを無効にする一般的な方法は、パスワードのハッシュでトークンに署名することです。したがって、パスワードが変更された場合、以前のトークンは自動的に検証されません。これをログアウトに拡張するには、ユーザーのレコードに最終ログアウト時間を含め、最終ログアウト時間とパスワードハッシュの組み合わせを使用してトークンに署名します。これには、トークンの署名を確認する必要があるたびにDBルックアップが必要ですが、おそらくユーザーをルックアップしています。
Travis Terry

4
ブラックリストをメモリに保持することで効率を上げることができます。そのため、無効化を記録し、期限切れの無効化を削除し、サーバーの起動時にのみ読み取るために、DBにヒットするだけで済みます。負荷分散アーキテクチャでは、インメモリブラックリストは、10秒などの短い間隔でDBをポーリングし、無効化されたトークンの露出を制限できます。これらのアプローチにより、サーバーは要求ごとのDBアクセスなしで要求の認証を続行できます。
ジョーラップ、

86

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

サーバーがJWTを作成し、シークレット(JWS)で署名してからクライアントに送信する場合、シークレットを変更するだけで既存のすべてのトークンが無効になり、古いトークンが突然無効になるため、すべてのユーザーが新しいトークンを取得して認証を受ける必要がありますサーバーへ。

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

明らかにこれは、すべての既存のトークンを期限切れにしたい緊急の場合にのみ機能します。トークンの期限切れごとに、上記の解決策の1つが必要です(短いトークンの期限切れ、またはトークン内に保存されているキーの無効化など)。


9
このアプローチは理想的ではないと思います。これは機能し、確かに単純ですが、公開鍵を使用している場合を想像してください。単一のトークンを無効にしたいときにいつでもその鍵を作成して再作成したくないでしょう。
Signus

1
@KijanaWoodard、公開鍵/秘密鍵のペアを使用して、RS256アルゴリズムの秘密として署名を有効に検証できます。ここに示す例では、シークレットを変更してJWTを無効にすることに言及しています。これは、a)署名と一致しない偽のpubkeyを導入するか、b)新しいpubkeyを生成することによって実行できます。そのような状況では、それは理想的とは言えません。
Signus

1
@Signus-落とし穴。公開鍵を秘密として使用していませんが、署名を検証するために公開鍵に依存している人もいます。
Kijana Woodard

8
これは非常に悪い解決策です。JWTを使用する主な理由は、ステートレスで拡張性があることです。動的シークレットを使用すると、状態が発生します。サービスが複数のノード間でクラスター化されている場合、新しいトークンが発行されるたびにシークレットを同期する必要があります。シークレットをデータベースまたはその他の外部サービスに保存する必要があります。これは、Cookieベースの認証を再発明するだけです
Tuomas Toivonen

5
@TuomasToivonen。ただし、シークレットを使用してJWTに署名し、同じシークレットを使用してJWTを検証できる必要があります。したがって、保護されたリソースにシークレットを保存する必要があります。シークレットが危険にさらされている場合は、それを変更し、その変更を各ノードに配布する必要があります。クラスタリング/スケーリングを備えたホスティングプロバイダーでは、通常、サービスにシークレットを保存して、これらのシークレットを簡単かつ信頼性の高い方法で配布できます。
ローマー2017

67

これは主に、@ mattwayによる回答をサポートおよび構築する長いコメントです

与えられた:

このページで提案されている他のソリューションのいくつかは、すべてのリクエストでデータストアにアクセスすることを推奨しています。すべての認証リクエストを検証するためにメインデータストアにアクセスすると、他の確立されたトークン認証メカニズムの代わりにJWTを使用する理由が少なくなります。毎回データストアにアクセスする場合、ステートレスではなく、本質的にJWTをステートフルにしました。

(サイトが大量の未承認のリクエストを受け取った場合、JWTはデータストアにアクセスすることなくそれらを拒否します。これは便利です。おそらく他の使用例もあるでしょう。)

与えられた:

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

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

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

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

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

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

これらの場合、トークンの有効期限を待つことはできません。トークンの無効化はすぐに行われる必要があります。また、悪意のある目的かどうかに関係なく、クライアントが古いトークンのコピーを保持して使用しないことを信頼することはできません。

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

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

それでも、初期JWT認証を渡すすべての認証リクエストに対してメモリ内のdbを呼び出す必要がありますが、ユーザーセット全体のキーをそこに格納する必要はありません。(これは、特定のサイトにとって重要な場合とそうでない場合があります。)


15
あなたの答えには同意しません。データベースにアクセスしても、ステートフルになることはありません。バックエンドに状態を保存します。JWTは作成されていないため、リクエストごとにデータベースにアクセスする必要はありません。JWTを使用するすべての主要なアプリケーションは、データベースによってサポートされています。JWTは完全に異なる問題を解決します。en.wikipedia.org/wiki/Stateless_protocol
Julian

6
@ジュリアン、これについてもう少し詳しく教えてもらえますか?JWTは実際にどの問題を解決しますか?
zero01alpha

8
@ zero01alpha認証:これは、JWTを使用するための最も一般的なシナリオです。ユーザーがログインすると、後続の各リクエストにはJWTが含まれ、ユーザーはそのトークンで許可されているルート、サービス、リソースにアクセスできます。情報交換:JSON Webトークンは、パーティ間で情報を安全に送信するための優れた方法です。JWTに署名できるので、送信者が送信者の本人であることを確認できます。参照してくださいjwt.io/introduction
ジュリアン

7
@ジュリアン私はあなたの意見の相違に同意しません:) JWTは問題を解決し(サービスの場合)、特定のクライアントの承認情報を提供する集中型エンティティにアクセスする必要があります。したがって、サービスAとサービスBは、クライアントXが何かを実行する権限を持っているかどうかを調べるためにリソースにアクセスする必要がある代わりに、Xからトークンを受け取ります。パーティー)。とにかく、JWTは、特に複数のサービスプロバイダーによって制御されている場合に、システム内のサービス間の共有状態を回避するのに役立つツールです。
リバノフ

1
また、jwt.io If the JWT contains the necessary data, the need to query the database for certain operations may be reduced, though this may not always be the case.
introduction

43

ユーザーモデルのjwtバージョン番号を記録しておきます。新しいjwtトークンは、バージョンをこれに設定します。

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

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


15
これは興味深いアイデアです。トークンの目的の一部として、ステートレスであり、データベースを使用する必要がないことがバージョンの保存場所だけです。ハードコードされたバージョンはバンプすることを難しくし、データベースのバージョン番号はトークンを使用することのいくつかの利点を打ち消します。
スティーブンスミス

13
おそらく、すでにトークンにユーザーIDを格納しており、データベースにクエリを実行して、ユーザーが存在するか、APIエンドポイントへのアクセスが許可されているかを確認します。したがって、jwtトークンのバージョン番号をユーザーのバージョン番号と比較して、余分なdbクエリを実行していません。
DaftMonk 14

5
データベースにまったく触れない検証でトークンを使用する可能性がある多くの状況があるので、私はおそらく言ってはいけません。しかし、この場合、回避するのは難しいと思います。
DaftMonk 14

11
ユーザーが複数のデバイスからログインした場合はどうなりますか?それらすべてで1つのトークンを使用する必要がありますか、それともログインすると以前のトークンがすべて無効になりますか?
meeDamian 2014

10
@SergioCorreaに同意します。これにより、JWTは他のトークン認証メカニズムとほとんど同じようにステートフルになります。
Ed J

40

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

目標:

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

ソリューション:

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

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

短所:

  • 更新トークンリクエストでデータストアルックアップを実行する必要があります。
  • 無効なトークンは、アクセストークンのTTLに対して引き続き機能します。

長所:

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

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


ある人がコンピュータから立ち上がって別の人に同じコンピュータを使用させるシナリオについてはどうですか?1人目はログアウトし、ログアウトすると2人目が即座にブロックされます。2人目が平均的なユーザーである場合、クライアントはトークンを削除することで簡単にユーザーをブロックできます。ただし、2番目のユーザーにハッキングスキルがある場合、ユーザーにはまだ有効なトークンを回復して1番目のユーザーとして認証する時間があります。遅滞なく、すぐにトークンを無効にする必要を回避する方法はないようです。
Joe Lapp、2016年

5
または、セッション/ローカルストレージまたはCookieからJWTを削除することもできます。
カミルキエチェフスキ16

1
@Ashtonianに感謝します。徹底的な調査を行った後、私はJWTを放棄しました。秘密鍵を保護するために非常に長い時間を費やさない限り、または安全なOAuth実装に委任しない限り、JWTは通常のセッションよりもはるかに脆弱です。私の完全なレポートを参照してください:by.jtl.xyz/2016/06/the-unspoken-vulnerability-of-jwts.html
Joe Lapp

2
更新トークンの使用は、ブラックリストを許可するための鍵です。グレート説明:auth0.com/blog/...
ロメール

1
これは、有効期間が短いアクセストークンとブラックリストに登録できる有効期間が長いリフレッシュトークンを組み合わせているため、私には最良の答えのようです。ログアウト時に、クライアントはアクセストークンを削除して、2番目のユーザーがアクセスできないようにする必要があります(アクセストークンはログアウト後も数分間有効のままです)。@Joe Lappによると、ハッカー(2番目のユーザー)は、削除された後でもアクセストークンを取得します どうやって?
M3RS 2018

14

私が検討してきたアプローチiatは、JWT に常に(発行された)値を含めることです。次に、ユーザーがログアウトするときに、そのタイムスタンプをユーザーレコードに保存します。JWTを検証するときiatは、を最後にログアウトしたタイムスタンプと比較するだけです。iatが古い場合は無効です。はい、DBに移動する必要がありますが、JWTが有効である場合はとにかくユーザーレコードを常にプルします。

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

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


1
良い考え!「1つのデバイス」の問題を解決するには、これをログアウトではなく緊急機能にする必要があります。それより前に発行されたすべてのトークンを無効にする日付をユーザーレコードに保存します。のようなものtoken_valid_after、または何か。驚くばかり!
OneHoopyFrood 2015年

1
@OneHoopyFroodねえあなたは私がより良い方法でアイデアを理解するのに役立つサンプルコードを持っていますか?本当にありがとうございます!
alexventuraio 2016年

2
他のすべての提案されたソリューションと同様に、このソリューションではデータベースルックアップが必要です。このルックアップを回避することがここで最も重要なため、この質問が存在するのはこのためです。(パフォーマンス、スケーラビリティ)。通常の状況では、ユーザーデータを取得するためにDBルックアップは必要ありません。すでにクライアントから取得しています。
Rob Evans

9

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

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


1
トークンをどのように拒否しますか?簡単なサンプルコードを見せてもらえますか?
alexventuraio

1
if (jwt.issue_date < user.last_pw_change) { /* not valid, redirect to login */}
Vanuan '25年

15
DBルックアップが必要です!
Rob Evans

5

ユーザーのドキュメント/レコードのDBに「last_key_used」フィールドを設定できます。

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

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

次に、たとえばユーザーがログアウトするとき、またはトークンを無効にする場合は、その「last_key_used」フィールドを別のランダムな値に変更するだけで、その後のチェックはすべて失敗し、ユーザーはユーザーでログインして再度パスする必要があります。


これは私が検討してきた解決策ですが、次の欠点があります。(1)ランダムをチェックするために各リクエストでDBルックアップを実行している(セッションではなくトークンを使用する理由を無効にする)か、または更新トークンの有効期限が切れた後、断続的にのみチェックします(ユーザーがすぐにログアウトしたり、セッションがすぐに終了したりしないようにします)。(2)ログアウトすると、すべてのブラウザーとすべてのデバイスからユーザーがログアウトされます(これは、従来は予期されていなかった動作です)。
Joe Lapp、2016年

ユーザーがログアウトするときにキーを変更する必要はありません。ユーザーがパスワードを変更するとき、または-パスワードを提供する場合
NickVarcha

3

このようなメモリ内のリストを保持します

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

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


2
ほとんどの本番サイトは複数のサーバーで実行されるため、このソリューションは機能しません。Redisまたは同様のinterpocessキャッシュを追加すると、システムが大幅に複雑になり、ソリューションよりも多くの問題が発生します。
user2555515

@ user2555515すべてのサーバーをデータベースと同期できます。毎回データベースにヒットするかどうかはあなたの選択です。あなたはそれがどんな問題をもたらすかを知ることができます。
Eduardo

3

------------------------この回答には少し遅れますが、誰かに役立つかもしれません--------------- -----------

クライアント側から、最も簡単な方法は、ブラウザのストレージからトークンを削除することです。

しかし、ノードサーバー上のトークンを破棄したい場合はどうでしょうか。

JWTパッケージの問題は、トークンを破棄する方法または方法を提供しないことです。上記のJWTに関して、さまざまな方法を使用できます。しかし、ここでは、jwt-redisを使用します。

したがって、サーバーサイドでトークンを破棄するには、JWTの代わりにjwt-redisパッケージを使用できます

このライブラリ(jwt-redis)は、ライブラリjsonwebtokenの全機能を完全に繰り返しますが、重要な追加が1つあります。Jwt-redisを使用すると、トークンラベルをredisに保存して有効性を確認できます。redisにトークンラベルがないと、トークンは無効になります。jwt-redisでトークンを破棄するために、destroyメソッドがあります

それはこのように機能します:

1) npmからjwt-redisをインストールします

2)作成するには-

var redis = require('redis');
var JWTR =  require('jwt-redis').default;
var redisClient = redis.createClient();
var jwtr = new JWTR(redisClient);

jwtr.sign(payload, secret)
    .then((token)=>{
            // your code
    })
    .catch((error)=>{
            // error handling
    });

3)確認するには -

jwtr.verify(token, secret);

4)破壊する -

jwtr.destroy(token)

注意:JWTで提供されるのと同じように、トークンのサインイン中にexpiresInを提供できます。

これは誰かに役立つかもしれません


2

なぜjtiクレーム(nonce)を使用し、それをユーザーレコードフィールドとしてリストに格納しないのですか(dbに依存しますが、少なくともコンマ区切りのリストで問題ありません)。他の人がおそらくとにかくユーザーレコードを取得したいと指摘しているため、個別にルックアップする必要はありません。このようにして、さまざまなクライアントインスタンスに対して複数の有効なトークンを持つことができます(「ログアウトエブリウェア」はリストを空にリセットできます)。


はい、これ。おそらく、ユーザーテーブルと新しい(セッション)テーブルの間に1対多の関係を作成して、jtiクレームと共にメタデータを格納できるようにします。
Peter Lada

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

トークンの検証については、最初にトークンの有効期限を確認し、トークンの有効期限が切れていない場合はブラックリストを確認します。

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


4
トークンをブラックリストに入れると、無国籍になります
KeremBaydoğan17年

2

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

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

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

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


2

ユーザー文字列ごとに一意で、グローバル文字列は一緒にハッシュされます

JWTシークレット部分として機能することにより、個別およびグローバルの両方のトークンを無効化できます。リクエスト認証中のデータベースのルックアップ/読み取りを犠牲にして最大の柔軟性。また、ほとんど変更されないため、キャッシュも簡単です。

次に例を示します。

HEADER:ALGORITHM & TOKEN TYPE

{
  "alg": "HS256",
  "typ": "JWT"
}
PAYLOAD:DATA

{
  "sub": "1234567890",
  "some": "data",
  "iat": 1516239022
}
VERIFY SIGNATURE

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload), 
  HMACSHA256('perUserString'+'globalString')
)

where HMACSHA256 is your local crypto sha256
  nodejs 
    import sha256 from 'crypto-js/sha256';
    sha256(message);

使用例については、https://jwt.ioを参照してください(動的な256ビットのシークレットを処理するかどうかは不明です)


1
もう少し詳細で十分です
巨人

2
@giantas、マークが意味するところは署名部分だと思う。そのため、JWTの署名に単一の鍵のみを使用する代わりに、各クライアントに固有の鍵を結合します。したがって、ユーザーのすべてのセッションを無効にする場合は、そのユーザーのキーを変更し、システムのすべてのセッションを無効にする場合は、そのグローバルな単一キーを変更します。
トミーアリアプラダーナ

1

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

  1. を生成しunique hashredisJWT保存します。これはセッションと呼ぶことができます
    • 特定のJWTが行ったリクエストの数も保存します-jwtがサーバーに送信されるたびに、リクエストの整数をインクリメントします。(これはオプションです)

したがって、ユーザーがログインすると、一意のハッシュが作成され、redisに保存され、JWTに注入されます。

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

これから拡張して、JWTをさらに安全にすることができます。方法は次のとおりです。

特定のJWTが行ったXリクエストごとに、新しい一意のセッションを生成し、それをJWTに保存してから、前のセッションをブラックリストに登録します。

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


1
トークンに新しいハッシュを注入するのではなく、トークン自体をハッシュしてその値をredisに格納できます。
Frug

また、JWTを確認しaudjti申し立ててください。あなたは正しい道を進んでいます。
Peter Lada

1

ユーザートークンを取り消すことができるようにする場合は、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. トークンの無効化は簡単です。トークンをセッションテーブルから削除し、ユーザーのグループの新しいタイムスタンプを生成するだけです。

同じリストをメモリ(c#のアプリケーション)に保持でき、リクエストごとにデータベースにアクセスする必要がなくなります。リストは、アプリケーションの開始時にdbからロードできます
dvdmn '25年

1

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

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

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


0

JWTを使用しているときにすべてのデバイスからのログアウト機能を提供する必要がある場合は、回答します。このアプローチでは、リクエストごとにデータベースルックアップを使用します。サーバーがクラッシュしても、永続的なセキュリティ状態が必要だからです。ユーザーテーブルには2つの列があります。

  1. LastValidTime(デフォルト:作成時間)
  2. ログイン済み(デフォルト:true)

ユーザーからのログアウト要求があるたびに、LastValidTimeを現在の時刻に、Logged-Inをfalseに更新します。ログイン要求がある場合、LastValidTimeは変更しませんが、Logged-Inはtrueに設定されます。

JWTを作成すると、ペイロードにJWT作成時間が含まれます。サービスを承認するときに、3つの条件を確認します

  1. JWTは有効ですか
  2. JWTペイロードの作成時間がユーザーのLastValidTimeより大きいか
  3. ユーザーはログインしていますか

実用的なシナリオを見てみましょう。

ユーザーXには2つのデバイスA、Bがあります。彼は午後7時にデバイスAとデバイスBを使用してサーバーにログインしました(JWTの有効期限は12時間としましょう)。AとBの両方に、createdTime:7pmのJWTがあります。

午後9時に彼はデバイスBを紛失しました。彼はすぐにデバイスAからログアウトします。つまり、データベースXのユーザーエントリのLastValidTimeは「ThatDate:9:00:xx:xxx」で、Logged-Inは「false」です。

9:30に、Mr.ThiefはデバイスBを使用してログインを試みます。Logged-Inがfalseであってもデータベースをチェックするため、許可しません。

午後10時にMr.Xが自分のデバイスAからログインします。これで、デバイスAは作成された時間(午後10時)のJWTを持ちます。これで、データベースのログインが「true」に設定されました

午後10時30分に、Thief氏がログインを試みます。ログインはtrueですが。データベースのLastValidTimeは午後9時ですが、BのJWTは午後7時に時間を作成しました。したがって、彼はサービスへのアクセスを許可されません。したがって、パスワードなしでデバイスBを使用すると、1つのデバイスがログアウトした後、作成済みのJWTを使用できません。


0

Keycloak(私が取り組んできた)のようなIAMソリューションは、次のようなトークン取り消しエンドポイントを提供します

トークン失効エンドポイント /realms/{realm-name}/protocol/openid-connect/revoke

ユーザーエージェント(またはユーザー)をログアウトするだけの場合は、エンドポイントも呼び出すことができます(これにより、トークンが無効になります)。繰り返しになりますが、Keycloakの場合、依存パーティはエンドポイントを呼び出すだけです。

/realms/{realm-name}/protocol/openid-connect/logout

詳細を知りたい場合はリンクしてください


-1

これは、トークンを検証するたびにDBルックアップを行わないと解決が非常に難しいようです。私が考えることができる代替案は、無効化されたトークンのブラックリストをサーバー側に保持することです。これは、サーバーが再起動時にデータベースをチェックして現在のブラックリストをロードするようにすることで、変更が発生して再起動後も変更が永続化されるたびにデータベースで更新する必要があります。

ただし、サーバーメモリ(並べ替えのグローバル変数)に保持すると、複数のサーバーを使用している場合、複数のサーバー間でスケーラブルにならないため、共有Redisキャッシュに保持できます。再起動が必要な場合に備えて、データをどこかに(データベース?ファイルシステム?)保持するように設定し、新しいサーバーが起動するたびに、Redisキャッシュにサブスクライブする必要があります。

ブラックリストの代わりに、同じソリューションを使用して、これを他のセッションと同じようにセッションごとにredisに保存されたハッシュで行うことができます 、応答ポイント(必ずそれは、多くのユーザーはしかし、ログインして、より効率的であるではありません)。

ひどく複雑に聞こえますか?それは私にはありません!

免責事項:私はRedisを使用していません。


-1

axiosまたは同様のpromiseベースのhttpリクエストlibを使用している場合は、.then()パーツ内部のフロントエンドでトークンを破棄するだけです。ユーザーがこの関数を実行した後、応答.then()部分で起動されます(サーバーエンドポイントからの結果コードは200である必要があります)。ユーザーがデータの検索中にこのルートをクリックした後、データベースフィールドuser_enabledがfalseの場合、トークンの破棄がトリガーされ、ユーザーは即座にログオフされ、保護されたルート/ページへのアクセスが停止されます。ユーザーが永続的にログオンしている間、トークンが期限切れになるのを待つ必要はありません。

function searchForData() {   // front-end js function, user searches for the data
    // protected route, token that is sent along http request for verification
    var validToken = 'Bearer ' + whereYouStoredToken; // token stored in the browser 

    // route will trigger destroying token when user clicks and executes this func
    axios.post('/my-data', {headers: {'Authorization': validToken}})
     .then((response) => {
   // If Admin set user_enabled in the db as false, we destroy token in the browser localStorage
       if (response.data.user_enabled === false) {  // user_enabled is field in the db
           window.localStorage.clear();  // we destroy token and other credentials
       }  
    });
     .catch((e) => {
       console.log(e);
    });
}

-3

トークンをユーザーテーブルに保存するだけで、ユーザーがログインすると新しいトークンが更新され、認証がユーザーの現在のjwtと同じになります。

これは最善の解決策ではないと思いますが、それは私にとってはうまくいきます。


2
もちろん最高ではありません!dbにアクセスできるユーザーはだれでも簡単に偽装できます。
user2555515

1
@ user2555515このソリューションは、データベースに保存されているトークンと同様に、データベースに保存されているトークンが暗号化されている場合に正常に機能します。Stateless JWTStateful JWT(セッションと非常によく似ています)には違いがあります。 Stateful JWTトークンのホワイトリストを維持することでメリットが得られます。
TheDarkIn1978
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.