JWTによるサーバ認証と認証

15005 ワード

質問する
少し前にプロジェクトを開発・配置した.
テストを準備して、サーバがさまざまな状況で正常に動作していることを確認しています.
変なところが見えてきました.
「アドレス」ウィンドウに「サーバー・アドレス/リソース/識別子」と入力すれば、誰でも使用できます.
返事をもらう.
ソリューション
この問題を解決するために、解決策を探しました.だいたい2種類あります.
session
1つ目はセッションによるユーザ認証です.
この方法の問題は、サーバがステータスを維持しなければならないことです.
私のプロジェクトはnodejsで実行されています.
このプロジェクトでは,状態保持を実現する方法は2つある.

  • プロセス間通信
    NodejsはCPUコアを使用します.Nodeとして実行されるサーバは、負荷が増加すると、複数のカーネル上でプロセスを実行する必要があります.したがって,状態保持を実現するためにはプロセス間で通信が必要であり,この方法は状況を複雑にする.

  • キャッシュ
    プロセスの先頭にスローを保存します.これにより、複数のプロセスは、キャッシュ内でセッション情報を迅速に維持することができる.しかし、私のプロジェクトはclientがステータス管理を行うので、サーバのステータスを維持する必要はないと思います.
  • jwt
    2つ目はjson web tokenを使用する方法です.
    このメソッドは、認証をサーバに要求したユーザーにトークンを発行します.ユーザーは次のことができます.
    リクエストから、コインを含むリクエスト方式.サーバはユーザ状態を維持する必要はなく,トークンの有効性を判断するだけでよい.
    もちろん、コインは奪われる可能性があります.
    対応方式はaccessTokenとrefreshTokenである.
    accessTokenは、有効期間を短縮し、期限が切れたらrefreshTokenを使用してaccessTokenを再発行する方法です.
    これを行うと、accessTokenを奪ってもあまり使用されません.refreshTokenを奪った場合、データベースから削除してアクセスをブロックできます.
    イニシアチブ
    実施前にシナリオを作成した.
    資格認定
    登録またはログイン
  • お客様は、会員登録または登録を要求します.
  • サーバは、accessToken、refreshTokenを送信して応答する.このとき、refreshTokenはデータベースに格納されます.
  • clientは、応答するaccessToken、refreshTokenをローカルに保存する.
  • 承認
    accessTokenが期限切れでない場合
  • clientはaccessTokenをヘッダ要求に入れる.
  • サーバはaccessTokenをチェックして応答します.
  • accessTokenは期限切れで、refreshTokenは期限切れではありません.
  • clientはaccessTokenをヘッダ要求に入れる.
  • サーバはaccessTokenの確認後に期限切れになるため、403 Forbiddenに応答する.
  • client 403が受信された場合、refreshTokenはタイトルに要求される.
  • サーバは、refreshTokenをチェックし、新しいリリースの応答にaccessTokenを含めます.
  • clientは応答のaccessTokenを格納する.
  • accessTokenもrefreshTokenも期限切れ
  • clientはaccessTokenをヘッダ要求に入れる.
  • サーバはaccessTokenの確認後に期限切れになるため、403 Forbiddenに応答する.
  • client 403が受信された場合、refreshTokenはタイトルに要求される.
  • サーバは、refreshTokenの確認後に期限が切れたため、403 Forbiddenに応答する.
  • clientローカルトークンをキャンセルして終了します.
  • tokenが存在しないか無効です
  • サーバ応答401.
  • インプリメンテーション
    jwtを用いた認証と認可を実現するには,2種類が必要である.
    トークンリリース関数
    accessTokenとrefreshTokenの関数を発行します.
    // 사용자의 id, access토큰인지 refresh토큰인지, 만료시간을 받아서 토큰을 발행한다.
    const jwt = require("jsonwebtoken");
    
    const issueAtoken = (id, which, expiresIn) => {
      const token = jwt.sign({ id, which }, process.env.JWTSECRET, { expiresIn });
      return token;
    };
    
    会員登録または登録時にコインを発行します.
     ...
     
     const accessToken = issueAtoken(userId, "access", "60m");
     const refreshToken = issueAtoken(userId, "refresh", "1800m");
    
     ...
     
     res.json({
    	code: 200,
        accessToken,
        refreshToken,
       	...
     });
    
    トークン確認ミドルウェア
    その後、すべてのリクエストに対して確認トークンを使用するミドルウェア.
    const jwt = require("jsonwebtoken");
    
    const verifyToken = async (req, res, next) => {
      try {
        // 토큰이 유효한지 확인한다. 유효하지 않다면 error를 던진다.
        const decoded = jwt.verify(
          req.headers.authorization,
          process.env.JWTSECRET
        );
    
        // refreshToken이 온 경우 데이터베이스의 refreshToken과 비교한다.
        if (decoded.which === "refresh") {
          const { QUERY, EQ } = POOL;
          const row = await QUERY`SELECT refreshToken FROM RefreshTokens WHERE ${EQ(
            {
              userId: decoded.id,
            }
          )}`;
          
          // refreshToken이 탈취당한 경우 데이터베이스의 토큰을 삭제하여 방어할 수 있다.
          if (row[0].refreshToken !== req.headers.authorization) {
            return res.status(401).json({
              code: 401,
              message: "JsonWebTokenError",
            });
          }
    
          req.newAccessToken = issueAtoken(decoded.id, "access", "60m");
        }
    
        return next();
      } catch (error) {
    	console.log(error);
        
        // 토큰이 만료된 경우 403 Forbidden
        if (error.name === "TokenExpiredError") {
          res.json({
            code: 403,
            message: "TokenExpiredError",
          });
    
          return res.end();
        }
        // 토큰이 유효하지 않는 경우 401 Unauthorized
        if (error.name === "JsonWebTokenError") {
          res.status(401).json({
            code: 401,
            message: "JsonWebTokenError",
          });
          return res.end();
        }
    
        res.status(500);
        return res.end();
      }
    };
    残念な点
    既存のすべてのリクエストに対する応答は解決されましたが、まだ問題があります.
  • ログインリクエストまたは会員加入リクエストは、依然として応答します.
  • accessTokenが盗まれた場合、応答は有効時間内に行われる.
  • コインが盗まれたかどうか分かりません.