nginxとJWTを使用してマイクロサービスアプリケーションでリクエストを認証する

トレンドにとどまり、ウェブ開発ファッションのトレンドを追うために、私は最新のウェブアプリケーションをrubyのマイクロサービスセットとemberの「シック」クライアントとして実装することにしました。 私が直面した最初の問題の1つは、要求の認証でした。 クラシックなモノリシックアプリケーションですべてがシンプルな場合、Cookie、セッションを使用して、何らかの工夫を加えれば、すべてが初めてのようになります。



建築



ベースとして、 JWT -Json Web Tokenを選択しました。 これは、2者間でクレームを提出するためのオープンなRFC 7519標準です。 これは、Header.Payload.Signatureという形式の構造です。ヘッダーとペイロードは、base64でパックされたJSONハッシュです。 ペイロードはここで一見の価値があります。 原則として、client_idとユーザーに関するその他の情報のみを含めることができますが、これは良い考えではありません。そこに識別子キーのみを転送し、データ自体を別の場所に保存する方が良いです。 何でもデータウェアハウスとして使用できますが、特に他のタスクに役立つため、redisが最適であると思われました。 もう1つの重要なポイントは、トークンに署名するキーを指定することです。 最も簡単なオプションは1つの共有キーを使用することですが、これは明らかに最も安全なオプションではありません。 セッションデータをredisに格納するとすぐに、各トークンに一意のキーを生成してそこに格納することを妨げるものは何もありません。



認可を担当するサービスがトークンを生成することは明らかですが、誰がどのようにトークンをチェックしますか? 原則として、各マイクロサービスにチェックをプッシュできますが、これはそれらの最大の分離という考えと矛盾します。 各サービスには、トークンの処理とチェックのロジックが含まれている必要があり、redisへのアクセスも必要です。 いいえ、私たちの目標は、最終サービスに届くすべてのリクエストがすでに承認され、ユーザーに関するデータを運ぶアーキテクチャを取得することです(たとえば、特別なヘッダーで)。



NGinxでのJWTトークンの確認



ここから、この記事の主要部分に進みます。 すべてのリクエストが通過する何らかの中間要素が必要であり、彼はそれらを認証し、クライアントデータで埋めて送信します。 理想的には、サービスは軽量でスケーリングが容易である必要があります。 luaスクリプトを使用して認証ロジックを追加できるため、明らかな解決策はNGinxリバースプロキシです。 正確に言うと、OpenRestyを使用します-すぐに使える「グッズ」がたくさんあるnginxディストリビューションです。 美しさを高めるために、これらすべてをDockerコンテナの形式で実装します。



私は完全にゼロから始めなければなりませんでした。 JWT署名検証既に実装ている素晴らしいlua-resty-jwtプロジェクトがあります。 署名の保存用にredisキャッシュを使用する例もありますが、次のように終了するだけです。



  1. Authorizationヘッダーからトークンを取得
  2. 検証が成功した場合、セッションデータを取得し、X-Dataヘッダーで送信します
  3. 有効なJSONを取得するためにエラーを少しくし


作業の結果は、 resty-lua-jwtにあります。



nginx.confで、httpセクションのluaパッケージへのリンクを登録する必要があります。



http { ... lua_package_path "/lua-resty-jwt/lib/?.lua;;"; lua_shared_dict jwt_key_dict 10m; ... }
      
      





さて、リクエストを認証するために、ロケーションセクションを押すことが残っています:



 location ~ ^/api/(.*)$ { set $redhost "redis"; set $redport 6379; access_by_lua_file /lua-resty-jwt/jwt.lua; proxy_pass http://upstream/api/$1; }
      
      





このすべてを開始します。



 docker run --name redis redis docker run --link redis -v nginx.conf:/usr/nginx/conf/nginx.conf svyatogor/resty-lua-jwt
      
      





これで完了です...まあ、ほとんど。 また、セッションをredisに配置し、クライアントにトークンを提供する必要があります。 jwt.luaプラグインは、ペイロードセクションのトークンに{kid:SESSION_ID}を介したハッシュが含まれることを想定しています。 redisでは、このSESSION_IDには、少なくとも1つの秘密キーを持つハッシュが必要です。このハッシュには、署名を検証するための公開キーがあります。 データキーが存在する場合もあります。データキーが見つかると、そのコンテンツはX-Dataヘッダーのアップストリームサービスに移動します。 このキーに、シリアル化されたユーザーオブジェクト、または少なくともそのIDを追加して、アップストリームサービスが要求の送信元を理解できるようにします。



ログインとトークン生成



JWTを生成するための非常に多くのライブラリがあります。詳細な説明は次のとおりです。jwt.io私の場合、jwt gemを選択しました。 SessionController#createアクションは次のようになります



 def new user = User.find_by_email params[:email] if user && user.authenticate(params[:password]) if user.kid and REDIS.exists(user.kid) > 0 REDIS.del user.kid end key = SecureRandom.base64(24) secret = SecureRandom.base64(24) REDIS.hset key, 'secret', secret REDIS.hset key, 'data', {user_id: user.id}.to_json payload = {"kid" => key} token = JWT.encode payload, secret, 'HS256' render json: {token: token} else render json: {error: "Invalid username or password"}, status: 401 end end
      
      





次に、UI(エンバー、アンギュラー、またはモバイルアプリケーション)で、認可サービスからトークンを取得し、それをすべてのリクエストでAuthorizationヘッダーに渡す必要があります。 正確にこれを行う方法は、特定のケースに依存するため、cUrlの例を示します。



 $ curl -X POST http://default/auth/login -d 'email=user@mail.com' -d 'password=user' {"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJraWQiOiI2cDFtdFBrVnhUdTlVNngxTk5yaTNPSDVLcnBGVzZRUCJ9.9Qawf8PE8YgxyFw0ccgrFza1Uxr8Q_U9z3dlWdzpSYo"}% $ curl http://default/clients/v1/clients -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJraWQiOiI2cDFtdFBrVnhUdTlVNngxTk5yaTNPSDVLcnBGVzZRUCJ9.9Qawf8PE8Ygxy Fw0ccgrFza1Uxr8Q_U9z3dlWdzpSYo' {"clients":[]}
      
      



}% $ curl -X POST http://default/auth/login -d 'email=user@mail.com' -d 'password=user' {"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJraWQiOiI2cDFtdFBrVnhUdTlVNngxTk5yaTNPSDVLcnBGVzZRUCJ9.9Qawf8PE8YgxyFw0ccgrFza1Uxr8Q_U9z3dlWdzpSYo"}% $ curl http://default/clients/v1/clients -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJraWQiOiI2cDFtdFBrVnhUdTlVNngxTk5yaTNPSDVLcnBGVzZRUCJ9.9Qawf8PE8Ygxy Fw0ccgrFza1Uxr8Q_U9z3dlWdzpSYo' {"clients":[]}





あとがき



既成のソリューションがあるかどうかを尋ねるのは論理的でしょうか? MashapeからKongのみを見つけました。 他の誰かにとって、これは良いバリエーションになるでしょう。なぜなら、 さまざまなタイプの許可に加えて、彼はACLの操作、負荷の管理、ACLの適用などを知っています。 私の場合、スズメに銃を撃ちます。 さらに、Casandraデータベースにも依存しています。Casandraデータベースは、控えめに言っても、このプロジェクトにはまったく当てはまらず、まったく異質です。



PPS静かに「善良な人々」がカルマを漏らした。 そのため、プラス記号は非常に役立ち、Web開発のマイクロサービスに関する新しい記事を書くための良いモチベーションになります。




All Articles