[Nodejs] ログイン認証 - Cookie

ここに画像の説明を挿入

1. 認証とは何ですか?


  • 平たく言えば、現在のユーザーの身元を確認し、「あなたが自分である」ことを証明することです(たとえば、毎日出退勤する場合は、指紋を入力する必要があります。指紋と指紋が一致する場合)システムに入力すると、正常にパンチインされます)
  • インターネットでの認証:
    • ユーザー名とパスワードを使用してログインします
    • ログインリンクを電子メールで送信する
    • 認証コードを受け取る携帯電話番号
    • 電子メール/認証コードを受信できる限り、デフォルトであなたがアカウントの所有者になります。

2. 認可とは何ですか?


  • ユーザーは、ユーザーの特定のリソースにアクセスするためのアクセス許可をサードパーティのアプリケーションに付与します。
    • モバイル アプリケーションをインストールすると、APP は権限 (フォト アルバムへのアクセス、地理的位置など) を付与するかどうかを尋ねます。
    • WeChat アプレットにアクセスすると、ログイン時に、アプレットは許可 (ニックネーム、アバター、地域、性別などの個人情報の取得) を許可するかどうかを尋ねます。
  • 認可を達成する方法は次のとおりです: Cookie、セッション、トークン、OAuth

3. 資格情報とは何ですか?


認証と認可の前提は、訪問者の身元を示す媒体(証明書)が必要であるということです。

インターネット アプリケーションでは、一般的な Web サイト (Nuggets など) には、訪問者モードとログイン モードという 2 つのモードがあります。ツーリストモードでは、Webサイト上の記事を通常通り閲覧することができますが、記事を「いいね!」したり、収集したり、共有したりするには、ログインまたはアカウント登録が必要です。ユーザーがログインに成功すると、サーバーはユーザーが使用するブラウザにトークン (トークン) を発行します。このトークンは、ユーザーの身元を示すために使用されます。ブラウザはリクエストを送信するたびにこのトークンを取得し、使用できるようになります。ゲスト モードでは利用できない機能。

4. クッキーとは何ですか


  • HTTP はステートレス プロトコルです (トランザクション処理用のメモリがなく、サーバーはクライアントとサーバーのセッションが完了するたびにセッション情報を保存しません)。各リクエストは完全に独立しており、サーバーは現在の訪問を確認できません。送信者の情報だけでは、前回のリクエストの送信者と今回の送信者が同一人物であるかどうかはわかりません。したがって、セッション追跡 (誰がアクセスしているのかを知る) を実行するには、サーバーとブラウザーは、前後の 2 つのリクエストが同じブラウザーからのものであるかどうかをサーバーに通知するために使用される状態をアクティブに維持する必要があります。そして、この状態は Cookie またはセッションを通じて実現する必要があります
  • Cookie はクライアント側に保存されます: Cookie は、サーバーによってユーザーのブラウザーに送信され、ローカルに保存される小さなデータです。ブラウザーが次回同じサーバーに別のリクエストを行うときに、サーバーに運ばれて送信されます。
  • Cookie はクロスドメインではありません: 各 Cookie は単一のドメイン名にバインドされており、他のドメイン名では使用できません。第 1 レベルのドメイン名と第 2 レベルのドメイン名の間での共有使用は許可されます (ドメインに応じて)。
属性 説明する
名前=値 Cookie の名前と対応する値を設定するキーと値のペアは、文字列型である必要があります。値が Unicode 文字の場合、その文字をエンコードする必要があります。値がバイナリ データの場合は、BASE64 でエンコードする必要があります。
ドメイン Cookie が属するドメイン名を指定します。デフォルトは現在のドメイン名です。
Cookie が有効になるパス (ルーティング) を指定します。デフォルトは「/」です。
/abc に設定されている場合、/abc/read など、/abc の下にあるルートのみが Cookie にアクセスできます。
最大年齢 Cookie の有効期限 (秒単位)。整数の場合、Cookie は maxAge 秒後に期限切れになります。負の数の場合、Cookie は一時的な Cookie であり、ブラウザを閉じると無効になり、ブラウザはいかなる形式でも Cookie を保存しません。0の場合はCookieを削除することを意味します。デフォルトは -1 です。- 期限切れよりも優れています。
有効期限が切れます 有効期限は、一定の時点を過ぎるとクッキーが無効になる時間を設定します。
通常、ブラウザの Cookie はデフォルトで保存されますが、ブラウザを閉じてセッションを終了すると、Cookie も削除されます。
安全 Cookie が安全なプロトコルを使用してのみ送信されるかどうか。セキュリティ プロトコルには HTTPS、SSL などが含まれ、データはネットワーク上で送信される前に暗号化されます。デフォルトは false です。
secure 値が true の場合、Cookie は HTTP では無効で、HTTPS でのみ有効です。
httpのみ Cookie に httpOnly 属性が設定されている場合、JS スクリプトを通じて Cookie 情報を読み取ることはできませんが、Cookie はアプリケーションを通じて手動で変更できるため、XSS 攻撃をある程度まで防ぐことはできますが、完全に防ぐことはできません。安全

クッキーの特徴

  • Cookie はブラウザーにローカルに保存され、ブラウザーが閉じられる限り有効期限が切れずに存在します。
  • 通常の状況では、Cookie は暗号化されていないため、ユーザーは簡単に見ることができます。
  • ユーザーは Cookie を削除または無効にすることができます
  • Cookieは改ざんされる可能性がある
  • Cookieは攻撃に使用される可能性があります
  • Cookie のストレージは非常に小さく、通常は 4K のサイズです
  • リクエストを送信すると、ログイン情報が自動的に取得されます

5. クッキーの使用


① 設置

pnpm install cookie-parser --save

②はじめに

const cookieParser=require("cookie-parser");

③ミドルウェアの設定

app.use(cookieParser());

④クッキーをセットする

res.cookie("name",'zhangsan',{
    
    maxAge: 1000*60*60, httpOnly: true});
//res.cookie(名称,值,{配置信息})
获取cookie
req.cookies.name;

基本的な例を次に示します。

const express=require("express");
const cookieParser=require("cookie-parser");

var app=express();

//设置中间件
app.use(cookieParser());

app.get("/",function(req,res){
    
    
	res.send("首页");
});

//设置cookie
app.get("/set",function(req,res){
    
    
    //如果不进行任何设置,有效期默认为1个会话,浏览器关闭即失效
   // res.cookie('isLogin','true');
	res.cookie("userName",'张三',{
    
    maxAge: 1000*60*60, httpOnly: true});
	res.send("设置cookie成功");
});

//获取cookie
app.get("/get",function(req,res){
    
    
	console.log(req.cookies.userName);
	res.send("获取cookie成功,cookie为:"+ req.cookies.userName);
});

app.listen(8080);

set ルートにアクセスした場合は Cookie が設定され、get ルートにアクセスした場合は設定された Cookie の値が取得されます。もちろん、他のページで現在の Cookie を取得し続けて、Cookie の共有を実現することもできます。Cookie とセッションの両方で、Web ページの応答ヘッダーに set-cookie が表示されます。

6. Cookieの暗号化について


Cookie の暗号化は、データ セキュリティの重要な部分である Cookie の平文情報をクライアント ユーザーが取得できないようにするためのものです。一般に、Cookie を保存するときに Cookie 情報を暗号化するか、res.cookie のオプション オブジェクトの signed 属性を true に設定します。

const express = require("express");
const cookieParser = require("cookie-parser");

var app = express();
app.use(cookieParser('secret'));//签名 (加密) 要指定秘钥 ,什么名字都星行,列如:"xiaoxuesheng"

app.get("/",function(req,res){
    
    
	res.send("主页");
});

//获取cookie
app.use(function(req,res,next){
    
    
	console.log(req.signedCookies.name);
	next();
});

//设置cookie
app.use(function(req,res,next){
    
    
	console.log(res.cookie("name","zhangsan",{
    
    httpOnly: true,maxAge: 200000,signed: true}));
	res.end("cookie为:"+req.signedCookies.name);
});

app.listen(8080);

7. Cookie を使用する際に考慮すべき問題


  • クライアント上に保存されるためクライアントによる改ざんが容易であり、利用前に合法性の検証が必要
  • ユーザーのパスワードや口座残高などの機密データを保存しないでください
  • httpOnly を使用してセキュリティをある程度向上させます
  • Cookie のサイズを最小限に抑え、保存できるデータ量は 4kb を超えることはできません
  • 正しいドメインとパスを設定してデータ送信を削減します。
  • Cookie はドメインを越えることはできません
  • ブラウザーは Web サイトに対して最大 20 個の Cookie を保存できますが、ブラウザーは通常 300 個の Cookie しか許可しません。
    モバイル端末は Cookie をあまりサポートしておらず、セッションは Cookie に基づいて実装する必要があるため、モバイル端末では一般的にトークンが使用されます。

8. セッションとは


  • セッションとは何ですか? セッションはクライアントのステータスを記録するためのもう 1 つのメカニズムです。違いは、Cookie がクライアントのブラウザーに保存されるのに対し、セッションはサーバーに保存されることです。クライアントのブラウザがサーバーにアクセスすると、サーバーはクライアントの情報を何らかの形式でサーバーに記録します。セッションです。クライアントのブラウザが再度アクセスするときは、セッションからクライアントのステータスを取得するだけで済みます。セッションは特別な Cookie です。Cookie はクライアント側に保存され、セッションはサーバー側に保存されます。
  • なぜセッションを使用するのでしょうか? Cookie はクライアント側に存在し、そのストレージ サイズは制限されているため、最も重要なことはユーザーがそれを表示でき、自由に変更できることですが、これは非常に安全ではありません。では、世界中の情報を安全かつ便利に読むにはどうすればよいでしょうか? そこでこの時点で、新しいストレージ セッション メカニズムである session が誕生しました。
    画像-20220620212335597

セッション認証プロセス:

  • ユーザーが初めてサーバーをリクエストすると、サーバーはユーザーが送信した関連情報に基づいて、対応するセッションを作成します。
  • リクエストが返されると、このセッションの一意の識別情報である SessionID がブラウザに返されます。
  • ブラウザはサーバーから返された SessionID 情報を受信すると、この情報を Cookie に保存し、Cookie は SessionID がどのドメイン名に属しているかを記録します。
  • ユーザーが 2 回目にサーバーにアクセスすると、リクエストはこのドメイン名に Cookie 情報があるかどうかを自動的に判断します。Cookie 情報がある場合は、それも自動的にサーバーに送信され、サーバーはセッション ID を取得します。 Cookieを取得し、SessionID情報を基に該当するSessionを検索し、見つからない場合は、ユーザーがログインしていないか、ログインが無効であることを意味し、Sessionが見つかった場合は、ユーザーがログインしていることを証明します。にあり、次の操作を実行できます。

上記のプロセスによれば、SessionID は Cookie と Session を接続するブリッジであり、ほとんどのシステムはこの原則に基づいてユーザーのログイン ステータスも検証します。

9. Cookieとセッションの違い


例を挙げると、
Cookie は理髪店に会員カードをもらいに行くようなもので、次回会員カードを持って行きます (レスポンスヘッダーに Cookie を設定し、ドメイン名を変更した後、各リクエストヘッダーにはセッションは理髪店に行って行うようなものです。
カードを購入しましたが、カードはそこに残っています。カード番号を覚えておいてください。

  • セキュリティ: セッションは Cookie より安全です。セッションはサーバー側に保存され、Cookie はクライアント側に保存されます。
    アクセス値の種類が異なります。Cookie は文字列データの保存のみをサポートし、他の種類のデータを設定したい場合は文字列に変換する必要があり、Session は任意のデータ型を保存できます。
  • さまざまな有効期間: Cookie は長期間持続するように設定できます。たとえば、よく使用するデフォルトのログイン機能、セッションの有効期限は一般に短く、クライアントが閉じられる (デフォルト) か、セッションが終了すると期限切れになります。タイムアウトになります。
  • ストレージ サイズは異なります。1 つの Cookie によって保存されるデータは 4K を超えることはできず、セッションに保存できるデータは Cookie のデータよりもはるかに大きくなりますが、アクセス数が多すぎると大量のデータを消費します。サーバーリソース。

10. セッションの利用


①Express-Sessionをインストールする

pnpm install express-session --save

② Express-Sessionモジュールの導入

const session=require("express-session");

③セッションを設定する

app.use(session(options));

メインメソッド: セッション(オプション)

セッションのストレージはオプションで設定されますが、セッション ID を除き、セッション内のデータは Cookie に保存されません。

パラメータ

 cookie: {
    
    
  // Cookie Options
  // 默认为{ path: '/', httpOnly: true, secure: false, maxAge: null }
   /** maxAge: 设置给定过期时间的毫秒数(date)
  * expires: 设定一个utc过期时间,默认不设置,http>=1.1的时代请使用maxAge代替之(string)
  * path: cookie的路径(默认为/)(string)
  * domain: 设置域名,默认为当前域(String)
  * sameSite: 是否为同一站点的cookie(默认为false)(可以设置为['lax', 'none', 'none']或 true)
  * secure: 是否以https的形式发送cookie(false以http的形式。true以https的形式)true 是默认选项。 但是,它需要启用 https 的网站。 如果通过 HTTP 访问您的站点,则不会设置 cookie。 如果使用的是 secure: true,则需要在 express 中设置“trust proxy”。
  * httpOnly: 是否只以http(s)的形式发送cookie,对客户端js不可用(默认为true,也就是客户端不能以document.cookie查看cookie)
  * signed: 是否对cookie包含签名(默认为true)
  * overwrite: 是否可以覆盖先前的同名cookie(默认为true)*/
  },
    
  // 默认使用uid-safe这个库自动生成id
  genid: req => genuuid(),  
    
  // 设置会话的名字,默认为connect.sid
  name: 'value',  
  
  // 设置安全 cookies 时信任反向代理(通过在请求头中设置“X-Forwarded-Proto”)。默认未定义(boolean)
  proxy: undefined,
    
  // 是否强制保存会话,即使未被修改也要保存。默认为true
  resave: true, 
    
  // 强制在每个响应上设置会话标识符 cookie。 到期重置为原来的maxAge,重置到期倒计时。默认值为false。
  rolling: false,
    
  // 强制将“未初始化”的会话保存到存储中。 当会话是新的但未被修改时,它是未初始化的。 选择 false 对于实现登录会话、减少服务器存储使用或遵守在设置 cookie 之前需要许可的法律很有用。 选择 false 还有助于解决客户端在没有会话的情况下发出多个并行请求的竞争条件。默认值为 true。
  saveUninitialized: true,
    
  // 用于生成会话签名的密钥,必须项  
  secret: 'secret',
  
  // 会话存储实例,默认为new MemoryStore 实例。
  store: new MemoryStore(),
  
  // 设置是否保存会话,默认为keep。如果选择不保存可以设置'destory'
  unset: 'keep'

API で
req.sessionセッション データを保存またはアクセスするには、リクエスト属性 req.session を使用するだけです。これは、JS 開発に非常に適した JSON 形式でシリアル化を保存します。

次のコード例のように:

const express=require("express");
const session=require("express-session");
const MongoStore = require("connect-mongo");

var app=express();

//配置中间件
//session会自带一个httpOnly
app.use(
  session({
    
    
    name: 'session-id',
    secret: "this is session", // 服务器生成 session 的签名
    resave: true,     //每次是否都刷新到期时间
    saveUninitialized: true, //强制将为初始化的 session 存储(该session_id是没有用的)
    cookie: {
    
    
      maxAge: 1000 * 60 * 10,// 过期时间
      secure: false, // 为 true 时候表示只有 https 协议才能访问cookie
    },
	//自动在mongodb中创建一个数据库存储session,并且过期时间也会同步刷新
    store: MongoStore.create({
    
    
      mongoUrl: 'mongodb://127.0.0.1:27017/ds2_session',
      ttl: 1000 * 60 * 10 // 过期时间
  }),
  })
);

// 授权中间件,在这个之后的路由,除了错误处理,都是需要授权的。
app.use((req, res, next) => {
    
    
  //排除login相关的路由和接口(因为login就不需要重定向到login了)
  if (req.url.includes("login")) {
    
    
    next()
    return
  }
  if (req.session.user) {
    
    
    //重新设置以下sesssion
    req.session.mydate = Date.now()//加这个设置才能访问刷新过期时间
    next()
  } else {
    
    
    //是接口, 就返回错误码
    //不是接口,就重定向(因为ajax请求是不能重定向的,只能前端接收错误码做处理)
    req.url.includes("api")
      ? res.status(401).json({
    
     ok: 0 }) : res.redirect("/login")
  }
})

app.use('/login',function(req,res){
    
    
	//设置session
	req.session.userinfo='张三';
	res.send("登陆成功!");
});

app.use('/',function(req,res){
    
    
	//获取session
	if(req.session.userinfo){
    
    
		res.send("hello "+req.session.userinfo+",welcome");
	}else{
    
    
		res.send("未登陆");
	}
});

app.listen(8080);

ここに画像の説明を挿入

フロントエンドのエラー処理

update.onclick = () => {
    
    
  fetch("/api/user/6257ad33341e112715f25cb5", {
    
    
    method: "PUT",
    body: JSON.stringify({
    
    
      username: "修改的名字",
      password: "修改的密码",
      age: 1,
    }),
    headers: {
    
    
      "Content-Type": "application/json",
    },
  })
    .then((res) => res.json())
    .then((res) => {
    
    
      console.log(res);
      //session验证失败会返回ok:0
      if (res.ok === 0) {
    
    
        location.href = "/login";
      }
    });
};

11. エクスプレスセッションのその他の使用法


11.1 方法

① .regenerate(callback)
セッションを再生成するには、このメソッドを呼び出すだけです。完了すると、新しい SID とセッション インスタンスが req.session で初期化され、コールバックが呼び出されます。

② .destroy(callback) は
セッションを破棄し、req.session 属性の設定を解除します。完了すると、コールバックが呼び出されます。

**③ **. reload(callback) は
ストレージからセッション データを再ロードし、req.session オブジェクトを再設定します。完了すると、コールバックが呼び出されます。

④ .save(callback )****
セッションをストアに保存し、ストア上のコンテンツをメモリ内のコンテンツに置き換えます。

このメソッドは、セッション データが変更された場合、HTTP 応答の最後に自動的に呼び出されます。

リダイレクト、長時間存続するリクエスト、WebSocket などの特定の状況でこのメソッドを呼び出すと便利です。

⑤ .touch()は
.maxAgeプロパティを更新します。通常、セッション ミドルウェアが自動的に実行するため、呼び出す必要はありません。

次のデモでは、セッションを破棄してログアウトします。

app.use('/login',function(req,res){
    
    
	//设置session
	req.session.userinfo='张三';
	res.send("登陆成功!");
});

app.use('/loginOut',function(req,res){
    
    
	//注销session
	req.session.destroy(function(err){
    
    
		res.send("退出登录!"+err);
	});
});

app.use('/',function(req,res){
    
    
	//获取session
	if(req.session.userinfo){
    
    
		res.send("hello "+req.session.userinfo+",welcome to index");
	}else{
    
    
		res.send("未登陆");
	}
});

app.listen(8080);

ホームページに入ると情報が表示されない ログインルートに入ると自動的にセッションが設定される ホームページに戻るとセッション情報が表示される ログインアウトルートに入るとセッション情報がログアウトされる, そしてホームページに戻ると、「こんにちは、張三さん、インデックスへようこそ」と表示されます。

11.2 プロパティ

① .id
各セッションには固有の ID が関連付けられます。このプロパティは req.sessionID のエイリアスであり、変更できません。これは、セッション オブジェクトからセッション ID にアクセスできるようにするために追加されました。

② .cookie
各セッションは固有の cookie オブジェクトを持ちます。これにより、訪問者ごとにセッション Cookie を変更できます。たとえば、req.session.cookie.expiresfalse に設定すると、ユーザー エージェントの存続期間中のみ Cookie が存続するようにできます。

③ .Cookie.maxAge は
req.session.cookie.maxAge残り時間をミリ秒単位で返します。返されたオブジェクトであるreq.session.cookie.expiresプロパティを調整することもできますセキュリティの観点から、maxAge を使用する方が良いですが、有効期限はサーバーによって与えられ、カウントダウン後に自動的に消えます。また、有効期限はハードコーディングされた時間であるため、ブラウザの時間を変更して有効期限を誤魔化すのは簡単です。expiresDate()

④ .Cookie.originalMaxAge
プロパティは、セッション Cookie の元の maxAge をミリ秒単位で返します。

⑤ req.sessionID
ロードされたセッションのIDを取得するには、リクエストプロパティreq.sessionIDにアクセスします。これは、セッションのロード/作成時に設定される単なる読み取り専用の値です。

11.3 ストア

前述したように、サーバーはセッションを保存しますが、セッションはどこに保存されるのでしょうか? 構成セッション オプションにストアがあります。指定されていない場合、デフォルトで new MemoryStore() を使用してメモリに保存されます。メモリの特性の 1 つは、電源がオフになるかサーバーが再起動されるとデータが失われることです。そのため、通常は、ファイル ストアやデータベース Redis などの他のストア ミドルウェアを指定してセッションを保存できます。デフォルトのストアを表示したい場合は、事前に変数を作成しておき、ストアに名前を付けると、後でストアの API を使用して呼び出すことができます。

const store = new MemoryStore() // 创建个MemoryStore实例
app.use(session({
    
    
    ...
    store
}))

app.use((req, res, next) => {
    
    
  store.get(req.sessionId, (err, session) => {
    
    
    // 这里就可以操作内存中的store数据了。
  })
})
  • store.all(コールバック)

このオプションのメソッドは、ストア内のすべてのセッションを配列として取得するために使用されます。コールバックの最初のものはエラーで、2 つ目はセッションです。

  • store.destroy(sid, コールバック)

この必須メソッドは、セッション ID (sid) が指定されたストレージからセッションを破棄/削除するために使用されます。コールバックの対象はエラーです。

  • store.clear([コールバック])

このメソッドは、ストレージからすべてのセッションを削除するために使用されます。コールバック オブジェクトはエラーです。

  • store.length(コールバック)

このメソッドは、ストア内のすべてのセッションの数を取得するために使用されます。コールバックの最初の文字は error で、2 番目の文字は len です。

  • store.get(sid, コールバック)

このメソッドの最初のパラメータはセッション ID (sid) です。コールバックの最初のものはエラーで、2 つ目はセッションです。見つからなかった場合でもエラーは発生しませんが、セッションでは null または unknown が返されます。

  • store.set(sid、セッション、コールバック)

このメソッドは、セッションを作成または変更し、ストアに保存するために使用されます。コールバックの対象はエラーです。

  • store.touch(SID、セッション、コールバック)

このメソッドは、指定されたセッション ID (sid) とセッションを持つ対応するセッションに「タッチ」します。コールバックの対象はエラーです。

これは主にストアがアイドル状態のセッションを自動的に削除する場合に使用され、このメソッドは特定のセッションがアクティブであることをストアに通知し、アイドル タイマーをリセットする可能性があります。

12. Token(トークン)とは


12.1 アクセストークン

  • リソースインターフェイス (API) にアクセスするために必要なリソース認証情報
  • 単純なトークンの構成: uid (ユーザーの一意の ID)、time (現在の時刻のタイムスタンプ)、sign (署名。トークンの最初の数桁は、一定の長さの 16 進文字列に圧縮されます。ハッシュアルゴリズム)
  • 特徴:
    • サーバーはステートレスでスケーラブルです
    • モバイルデバイスのサポート
    • 安全性
    • プログラム間通話のサポート
  • トークン認証プロセス:
    画像-20221107232241864

① クライアントは、ユーザー名とパスワードを使用してログインを要求
します。 ② サーバーは、ユーザー名とパスワードを検証する要求を受け取ります。
③ 検証が成功すると、サーバーはトークンを発行し、クライアントにトークンを送信します。
④ クライアントが受信した後、トークンは、Cookie や localStorage などに保存されます。
⑤ クライアントがサーバーにリソースを要求するたびに、サーバーによって発行されたトークンを取得する必要があります。
⑥ サーバーはリクエストを受信し、クライアントがリクエストにトークンが含まれており、検証が成功すると、リクエストされたデータがクライアントに返されます。

  • 各リクエストはトークンを運ぶ必要があり、トークンは HTTP ヘッダーに配置される必要があります。
  • トークンベースのユーザー認証はステートレスなサーバー側の認証方法であり、サーバーはトークン データを保存する必要がありません。セッションのストレージ領域と引き換えにトークンの解析の計算時間を使用することで、サーバーへの負荷が軽減され、頻繁なデータベース クエリが削減されます。
  • トークンはアプリケーションによって完全に管理されるため、同一生成元ポリシーを回避できます。

12.2 リフレッシュトークン

  • 別の種類token——refresh token

  • refresh tokenアクセストークンの更新専用のトークンです。リフレッシュトークンがない場合は、アクセストークンをリフレッシュすることもできますが、リフレッシュのたびにログインユーザー名とパスワードを入力する必要があり、非常に面倒です。リフレッシュトークンを使用すると、この手間が軽減され、ユーザーによる追加操作を必要とせずに、クライアントが直接リフレッシュトークンを使用してアクセストークンを更新します。
    画像-20221107232317906

  • Access TokenAccesss Tokenの有効期限は比較的短く、期限切れによりAccesss Tokenが無効になった場合は、Refresh Tokenを使用して新しいTokenを取得できますが、Refresh Tokenも無効になった場合は再ログインしかできません。

  • Refresh Token有効期限はサーバーのデータベースに保存され、新しいアクセス トークンを申請するときにのみ検証されます。ビジネス インターフェイスの応答時間には影響せず、セッションのようにメモリに保持する必要もありません。大量のリクエストに対応するため。

12.3 トークンとセッションの違い

  • Sessionサーバーとクライアント間のセッション状態を記録する仕組みで、サーバーをステートフルにし、セッション情報を記録することができます。Token はトークン、つまりリソー​​ス インターフェイス (API) にアクセスするために必要なリソースの資格情報です。トークンはサーバーをステートレスにし、セッション情報を保存しません。
  • SessionこれはToken、ID 認証トークンとして、セッションよりもセキュリティが優れていることと矛盾しません。これは、各リクエストに署名があり、監視およびリプレイ攻撃を防ぐことができるためです。一方、セッションは通信のセキュリティを確保するためにリンク層に依存する必要があります。ステートフル セッションを実装する必要がある場合でも、セッションを追加してサーバー側に一部の状態を保存できます。
  • いわゆるSession認証はユーザー情報を に保存するだけですがSession、その予測不可能性のためSessionID、当面は安全であると考えられています。ただしToken、 または同様のメカニズムを指す場合はOAuth Token、認証と認可を提供します。認証はユーザーに対して行われ、認可は。其目的是让某 Appユーザーの情報にアクセスする権利を持つアプリに対して行われます。ここTokenがユニークです。他のアプリに転送したり、他のユーザーに転送したりすることはできません。Session 単純な認証のみが提供されます。つまり、これがある限りSessionID、このユーザーのすべての権限があるとみなされます。このデータはサイト上にのみ保存し、他の Web サイトやサードパーティのアプリと共有しないでください。簡単に言うと、ユーザー データをサードパーティと共有する必要がある場合、またはサードパーティによる API インターフェイスの呼び出しを許可する必要がある場合は、 を使用しますToken常に自分の Web サイトと自分のアプリだけであれば、何を使用するかは問題ではありません。

13.JWTとは


13.1 はじめに

  • JSON Web Token (略して JWT) は、現在最も人気のあるクロスドメイン認証ソリューションです。
    これは認証および認可のメカニズムです。
  • JWT は、Web アプリケーション環境間でクレームを渡すために実装されたオープンな JSON ベースの標準 (RFC 7519) です。JWT クレームは通常、リソース サーバーからリソースを取得するために、アイデンティティ プロバイダーとサービス プロバイダーの間で認証されたユーザー ID 情報を渡すために使用されます。たとえば、ユーザーのログインに使用されます。
  • JWT は、HMAC アルゴリズムまたは RSA 公開/秘密キーを使用して署名できます。デジタル署名の存在により、これらの送信情報は信頼されます。

JWT 認証プロセス:
まず、フロントエンドは、Web フォームを通じてユーザー名とパスワードをバックエンド インターフェイスに送信します。このプロセスは通常、リクエストですPOST推奨される方法は、機密情報が盗聴されることを避けるためにSSL暗号化された送信 ( HTTPS) を使用することです。バックエンドがユーザー名とパスワードを正常にチェックした後、ユーザー情報を含むデータがデータとして使用され、コード化されて結合さJWTますPayloadバックエンドがフロントエンドへのログインに成功した結果として返される文字列とJWT Header同様の文字列ですフロントエンドは、返された結果をブラウザに保存し、ログアウト時に保存した結果を削除することができ、リクエスト(解決策問題)を実行するたびにリクエスト ヘッダーの属性にその結果を入れます。フロントエンドをチェックし、署名が正しいかどうか、有効期限が切れているかどうか、受信者が自分であるかどうかなどの検証を行います。検証に合格した後、バックエンドは署名に含まれるユーザー情報を解析し、実行します。他の論理操作 (通常はユーザー情報などに基づいて権限を取得します) を実行し、結果を返します。Base64JWT TokenJWT Tokenlll.zzz.xxxJWT TokenJWT TokenJWT TokenHTTPAuthorizationXSSXSRFJWT TokentokenJWT Token
ここに画像の説明を挿入

13.2 トークンと JWT の違い

同じ:

  • どちらもリソースにアクセスするためのトークンです
  • ユーザー情報を記録できる
  • すべてサーバーをステートレスにする
  • 認証が成功した後にのみ、クライアントはサーバー上の保護されたリソースにアクセスできます。

違い:

  • トークン: サーバーは、クライアントから送信されたトークンを検証するときに、データベースにクエリを実行してユーザー情報を取得し、トークンが有効かどうかを検証する必要もあります。
  • JWT: トークンとペイロードは暗号化され、クライアントに保存されます。サーバーは、キーを使用して復号化して検証するだけで済みます (検証は JWT 自体によっても実装されます)。JWT はクエリ データベースをクエリしたり、クエリ データベースを削減したりする必要はありません。ユーザー情報と暗号化されたデータがそれ自体に含まれています。

14. なぜ JWT を使用するのですか?


従来のセッション認証の欠点

HTTP 自体がステートレス プロトコルであることはわかっています。つまり、ユーザーがユーザー認証のためにアプリケーションにユーザー名とパスワードを提供した場合、HTTP プロトコルは認証に合格した後、次のリクエストで認証状態を記録しません。ユーザーは再度認証する必要があります。HTTP プロトコルによれば、どのユーザーがリクエストを送信したかがわからないため、アプリケーションがどのユーザーがリクエストを送信したかを識別するには、ユーザーが最初の認証に成功した後にのみログインできます。 time 、ユーザーのログイン情報の一部をサーバーに保存します。このログイン情報は応答でブラウザに渡され、次のリクエストが行われたときにアプリケーションに送信できるように、Cookie として保存するように指示されます。 、アプリケーションがどのユーザーからのリクエストを識別できるようにするため、これが従来のセッションベースの認証プロセスです。
ここに画像の説明を挿入
ただし、従来のセッション認証には次の問題があります。

  • 各ユーザーのログイン情報はサーバーのセッションに保存されるため、ユーザー数が増えるとサーバーのオーバーヘッドが大幅に増加します。
  • セッションはサーバーの物理メモリに存在するため、分散システムではこの方法は失敗します。セッションは Redis に一律に保存できますが、システムの複雑さは間違いなく増加します。また、Redis を必要としないアプリケーションの場合、追加のキャッシュ ミドルウェアが無駄に導入されることになります。
  • セッションは Cookie に依存しており、携帯端末には Cookie がないことが多いため、ブラウザ以外のクライアントや携帯電話などには適用されません。
  • セッション認証は基本的に Cookie に基づいているため、Cookie が傍受されると、ユーザーはクロスサイト リクエスト フォージェリ攻撃に対して脆弱になります。また、ブラウザが Cookie を無効にしている場合、このメソッドも失敗します。
  • これは、フロントエンドとバックエンドの分離システムにはさらに適用できません。バックエンドの展開は複雑で、フロントエンドから送信されたリクエストは複数のミドルウェアを介してバックエンドに到達することが多く、セッションに関する情報は、 Cookie は複数回転送されます
  • セッション認証は Cookie に基づいており、Cookie はドメインを越えることができないため、セッション認証はドメインを越えることができず、シングル サインオンには適用されません。

JWT認証のメリット

従来のセッション認証方法と比較して、JWT には次のような利点があります。

  • 簡潔: JWT トークンはデータ量が少なく、送信速度が速い
  • JWT トークンは JSON 暗号化形式でクライアントに保存されるため、JWT は言語を超えて使用でき、原則としてあらゆる Web フォームがサポートされます。
  • セッション情報をサーバー側に保存する必要がない、つまり Cookie やセッションに依存しないため、従来のセッション認証の欠点がなく、特に分散マイクロサービスに適しています
  • シングル サインオンが容易: ID 認証にセッションを使用する場合、Cookie はドメインを越えることができないため、シングル サインオンを実現するのは困難です。ただし、認証にトークンを使用する場合、トークンは必ずしも Cookie ではなく、クライアント上の任意の場所のメモリに保存できるため、Cookie に依存しなくてもそのような問題は発生しません。
  • モバイルアプリケーションに最適: ID認証にSessionを使用する場合、サーバー側に情報を保存する必要があり、この方法はCookieに依存するため(SessionIdの保存にはCookieが必要)、モバイル端末には適していません

このような利点があるため、単一アプリケーションか分散アプリケーションかに関係なく、ユーザー認証には JWT トークンを使用することをお勧めします。

15. jwtアプリケーション


カプセル化方式
画像-20220623101216020

//jsonwebtoken 封装
const jwt = require("jsonwebtoken")
const secret = "dselegent"

const JWT = {
    
    
    //生成签名
    //expiresIn是过期时间,例'24h'
	//value是要传入的数据
    generate(value,expiresIn){
    
    
        return jwt.sign(value,secret,{
    
    expiresIn})
    },
    verify(token){
    
    
        try{
    
    
            return jwt.verify(token,secret)//返回的是解析后的token,原始数据+自带的数据构成的对象
        }catch(e){
    
    
            return false//通过上面按个方法会自动解出是否过期,可是会报错,所以用try-catch
        }
    }
}

module.exports = JWT
router/login.js

  async login(req, res, next) {
    
    
    const {
    
     username, password } = req.body;

    let data = await userService.login({
    
     username, password });//存储数据库
     //因为存储成功返回的data对象并不是简单的对象,不能直接用,只能取出要用的值
    if (data) {
    
    
      const token = jwt.generate({
    
    
        id:data._id,
        username:data.username
      },"10s")
      res.header("Authorization",token)//将token设置到响应头
      res.send({
    
    ok: 1});
    }
  }

ログイン.html

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
   //拦截器,
   axios.interceptors.request.use(function (config) {
    
    
        // console.log("请求发出前,执行的方法")
        // Do something before request is sent
        return config;
    }, function (error) {
    
    
        // Do something with request error
        return Promise.reject(error);
    });
    axios.interceptors.response.use(function (response) {
    
    
      console.log(response);
        // console.log("请求成功后 ,第一个调用的方法")
        const {
    
    authorization } = response.headers
        authorization && localStorage.setItem("token",authorization)
        return response;
    }, function (error) {
    
    
        return Promise.reject(error);
    });
</script>
    用户名:
    <input id="username" />
  <div>
    密码:
    <input type="password" id="password" />
  </div>
  <div><button id="login">登录</button></div>

<script>
  var username = document.querySelector('#username');
  var password = document.querySelector('#password');
  var login = document.querySelector('#login');

  login.onclick = () => {
    
    
    axios.post("/users/login", {
    
    
            username: username.value,
            password: password.value,
        }).then(res => {
    
    
            console.log(res.data)
            if (res.data.ok === 1) {
    
    
                //存储token
                location.href = "/"
            } else {
    
    
                alert("用户名密码不匹配")
            }
        })
  };
</script>

トークンの入力が必要なページ

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
  //拦截器,
  axios.interceptors.request.use(function (config) {
    
    
     //携带token
    const token = localStorage.getItem("token")
    config.headers.Authorization = `Bearer ${
      
      token}`
    return config;
  }, function (error) {
    
    
    return Promise.reject(error);
  });

  axios.interceptors.response.use(function (response) {
    
    
    console.log(response);
    const {
    
    authorization} = response.headers
    //这里是如果有新的token返回(说明这次发请求的没有过期),就重新设置
    //如果过期了,后端会发错误码,前端处理重定向登录
    authorization && localStorage.setItem("token", authorization)
    return response;
  }, function (error) {
    
    
    if(error.response.status===401){
    
    
      localStorage.removeItem("token")
      location.href = "/login"
    }
    return Promise.reject(error);
  });
</script>

<body>
<div>
  <div>
    用户名:
    <input id="username" />
  </div>
  <div>
    密码:
    <input type="password" id="password" />
  </div>
  <div>
    年龄:
    <input type="number" id="age" />
  </div>

  <div>
    <button id="loginpost">注册</button>
  </div>
  <div>
    <button id="update">更新</button>
  </div>
  <div>
    <button id="delete">删除</button>
  </div>
</div>

<table border="1">
  <thead>
    <tr>
      <th>名字</th>
      <th>年龄</th>
    </tr>
  </thead>
  <tbody></tbody>
</table>
<script>
  var register = document.querySelector('#loginpost');
  var username = document.querySelector('#username');
  var password = document.querySelector('#password');
  var age = document.querySelector('#age');
  let tbody = document.querySelector('tbody');

  register.onclick = () => {
    
    
    axios.post("/users", {
    
    
    username: username.value,
    password: password.value,
    age: age.value
  }).then(res => {
    
    
    console.log(res.data)
  })
  };

    axios.get("/users?page=1&limit=10").then(res => {
    
    
  res = res.data
  var tbody = document.querySelector("tbody")
  tbody.innerHTML = res.map(item => `
  <tr>
      <td>${
      
      item.username}</td>
      <td>${
      
      item.age}</td>
    </tr>`).join("")
})
    //退出登录
     exit.onclick = () => {
    
    
   localStorage.removeItem("token")
   location.href = "/login"
 }
</script>

トークン処理ミドルウェア

//node中间件校验
app.use((req,res,next)=>{
    
    
  // 如果token有效 ,next() 
  // 如果token过期了, 返回401错误
  if(req.url==="/login"){
    
    
    next()
    return;
  }
	//Authorization会变成authorization
    //链判断运算符如果?前面判断为真就会继续执行后面的,判断为假就不会执行后面
    //这里因为如果没有token,前面是undefined,去使用undefined是会报错的
    
    //如果有token就验证,没token就通过
    //(直接访问/能通过,但是有个那个页面自动获取数据的axios,在那里就会发送authorization请求头,进入token验证)
  const token = req.headers["authorization"]?.split(" ")[1]
  if(token){
    
    
    var payload = JWT.verify(token)
     //验证成功就生成一个新token重置有效时间,
    // 验证失败就返回错误码让前端跳到登录页
    if(payload){
    
    
      const newToken = JWT.generate({
    
    
        id:payload.id,
        username:payload.username
      },"1d")
      res.header("Authorization",newToken)
      next()
    }else{
    
    
      res.status(401).send({
    
    errCode:"-1",errorInfo:"token过期"})
    }
  }
})

おすすめ

転載: blog.csdn.net/weixin_43094619/article/details/131933006