DachangTechnologyAdvancedフロントエンドノードAdvanced
トッププログラマーの成長ガイドをクリックし、公開番号に注意してください
返信1、高度なノード交換グループに参加する
この記事では、次のことがわかります。
HTTPベースのフロントエンド認証の背景
クッキーが最も便利なストレージソリューションである理由と、クッキーを操作する方法は何ですか?
セッションスキームはどのように実装され、問題は何ですか
トークンスキームはどのように実装され、どのようにエンコードされ、改ざんされないようになっていますか?jwtは何をしますか?更新トークンの実装と意味
セッションとトークンの類似点と相違点、およびそれらの長所と短所は何ですか
シングルサインオンとは何ですか?ブラウザでの実装のアイデアと処理
州から
「HTTPステートレス」HTTPはステートレスであることがわかっています。つまり、HTTPリクエスターとレスポンダーの間の状態は維持できず、すべて1回限りであり、前のリクエストに何が起こったのかがわかりません。ただし、一部のシナリオでは、状態を維持する必要があります。最も一般的には、ユーザーがWeiboにログインするとき、投稿、フォロー、コメントはログインしたユーザーの状態である必要があります。「マーキング」の解決策は何ですか?::マーク::。
学校や会社では、入学した日から身分証明書と口座情報が入力され、カードが発行されます。今後は、このカードをスワイプするだけでアクセス制御が可能になります。で、そして公園での消費。
「フロントエンドストレージ」これには、1回限り、1つのストレージ、1つのエリア、管理が容易なものが含まれ、ログインインターフェイスはフロントエンドに直接返され、フロントエンドは次の方法を見つける必要があります。ストレージ。
カードを携帯しなければならないことが前提です。
フロントエンドを保存する方法はたくさんあります。
最も傲慢なのはグローバル変数に掛けることですが、これは「エクスペリエンスカード」であり、ページを更新すると消えます。
ハイエンドのものはCookieやlocalStorageなどに保存されます。これは「メンバーシップカード」です。どのように更新しても、ブラウザがクリアまたは期限切れにならない限り、常にこの状態を保持します。
フロントエンドストレージはここでは拡張されません。それを保存する場所があり、それを要求するときに、パラメーターにそれを綴り、インターフェースに持ってくることができます。
基礎:クッキー
しかし、フロントエンドはとても面倒なので、自分で保存する必要があり、それを取り出す方法を見つける必要があります。心配する必要のないものはありますか?
はい、クッキーです。Cookieもフロントエンドストレージの一種ですが、localStorageなどの他の方法と比較すると、HTTPヘッダーとブラウザー機能を利用して、Cookieをフロントエンドに認識されないようにすることができます。一般的なプロセスは次のとおりです。
マークアップを提供するインターフェースでは、HTTPリターンヘッダーのSet-Cookieフィールドを介して、ブラウザーに直接「シード」されます。
ブラウザがリクエストを開始すると、HTTPリクエストヘッダーのCookieフィールドを介してCookieがインターフェースに自動的に送信されます。
「構成:ドメイン/パス」
清華大学のキャンパスカードで北京大学に入学することはできません。
Cookieは、ドメイン(ドメイン)/パス(パス)を介して::"スペーススコープ"::を2つのレベルに制限します。
Domain属性は、ブラウザがHTTPリクエストを送信するときにこのCookieに添付するドメイン名を指定します。この属性が指定されていない場合、ブラウザはデフォルトで現在のURLの第1レベルドメイン名として設定します。たとえば、www.example.comはexample.comに設定され、exampleのサブドメインにアクセスすると将来的には、HTTPリクエストもこのCookieを持っていきます。サーバーが[Cookieの設定]フィールドで指定したドメイン名が現在のドメイン名に属していない場合、ブラウザはCookieを拒否します。Path属性は、ブラウザがHTTPリクエストを送信するときにこのCookieを添付するパスを指定します。ブラウザがPath属性がHTTPリクエストパスの最初の部分であることを検出する限り、このCookieをヘッダー情報に取り込みます。たとえば、PATH属性が/の場合、リクエスト/docsパスにもCookieが含まれます。もちろん、ドメイン名は一貫している必要があるという前提があります。— Cookies — JavaScript標準リファレンスチュートリアル(アルファ版)
「構成:有効期限/最大年齢」
卒業後、カードは機能しません。
Cookieは、Expiresの1つであるMax-Ageを介して:: "timerange"::を制限することもできます。
Expires属性は特定の有効期限を指定します。指定された時間が経過すると、ブラウザはCookieを保持しなくなります。その値はUTC形式です。このプロパティが設定されていないか、nullに設定されている場合、Cookieは現在のセッションでのみ有効です。ブラウザウィンドウが閉じられ、現在のセッションが終了すると、Cookieは削除されます。また、ブラウザは現地時間に応じてCookieの有効期限が切れるかどうかを判断します。現地時間は不正確であるため、サーバーが指定した時間にCookieが期限切れになることを保証する方法はありません。Max-Age属性は、Cookieが今後存在する秒数を指定します(60 * 60 * 24 * 365(つまり、1年))。この時間が経過すると、ブラウザはこのCookieを保持しなくなります。ExpiresとMax-Ageが同時に指定されている場合、Max-Ageの値が優先されます。Set-CookieフィールドでExpiresまたはMax-Age属性が指定されていない場合、このCookieはセッションCookieです。つまり、このセッションにのみ存在します。ユーザーがブラウザを閉じると、ブラウザはこのCookieを保持しなくなります。 。— Cookies — JavaScript標準リファレンスチュートリアル(アルファ版)
「構成:セキュア/HttpOnly」
カード所有者をスワイプすることを許可しないと規定している学校もあります(架空の素晴らしい学校です)。一部の学校では、カードにステッカーを貼ることを許可していません。
Cookieは::"usage"::を制限できます。
Secure属性は、ブラウザが暗号化されたプロトコルHTTPSの下でのみこのCookieをサーバーに送信できることを指定します。一方、現在のプロトコルがHTTPの場合、ブラウザはサーバーから送信されたSecure属性を自動的に無視します。このプロパティは単なるスイッチであり、値を指定する必要はありません。通信がHTTPSプロトコルの場合、このスイッチは自動的にオンになります。HttpOnly属性は、主にDocument.cookie属性、XMLHttpRequestオブジェクト、およびRequest APIがこの属性を取得できないため、JavaScriptスクリプトを介してCookieを取得できないことを指定します。このようにして、Cookieがスクリプトによって読み取られるのを防ぎ、ブラウザがHTTPリクエストを送信した場合にのみCookieが実行されます。— Cookies — JavaScript標準リファレンスチュートリアル(アルファ版)
「HTTPヘッダーはCookieを読み書きします」振り返ってみると、HTTPはCookieとその構成をどのように読み書きしますか?HTTPによって返されるSet-Cookieヘッダーは、「1つ(そして1つだけ)」のCookieをCookieキー+構成キーの形式でブラウザーに書き込むために使用されます。例えば:
Set-Cookie: username=jimu; domain=jimu.com; path=/blog; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
では、一度に複数のCookieを設定したい場合はどうなりますか?複数のSet-Cookieヘッダーを指定します(1つのHTTPリクエストで繰り返しが許可されます)
Set-Cookie: username=jimu; domain=jimu.com
Set-Cookie: height=180; domain=me.jimu.com
Set-Cookie: weight=80; domain=me.jimu.com
HTTPリクエストのCookieヘッダーは、現在の「スペース、時間、使用法」構成に準拠するすべてのCookieをサーバーに送信するためにブラウザーによって使用されます。スクリーニングの判断はブラウザで行うため、キー値が送信されている限り、設定内容を返す必要はありません。
Cookie: username=jimu; height=180; weight=80
「フロントエンドのCookieの読み取りと書き込み」フロントエンドはそれ自体でCookieを作成できます。サーバーによって作成されたCookieが追加されていない場合はHttpOnly
、おめでとうございます。彼が提供したCookieを変更することもできます。呼び出しdocument.cookie
はCookieを作成および変更でき、HTTPと同様document.cookie
に、一度に操作できるCookieは1つだけです。
document.cookie = 'username=jimu; domain=jimu.com; path=/blog; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly';
呼び出しdocument.cookie
はCookieを読み取ることもでき、HTTPと同様に、HttpOnly
Cookie以外のすべてを読み取ることができます。
console.log(document.cookie);
// username=jimu; height=180; weight=80
(Cookie属性だけですが、読み取りと書き込みの動作が異なるのはなぜですか?理解するために取得/設定してください)「CookieはHTTPリクエストステータスを維持するための基礎です」Cookieを理解した後、CookieがHTTPリクエストステータスを維持するための最も便利な方法であることがわかります。ほとんどのフロントエンド認証の問題はCookieによって解決されます。もちろん、他の保存方法も使用できます(多かれ少なかれ後述します)。では、ストレージツールを使用して、次は何をしますか?
アプリケーションスキーム:サーバーセッション
今振り返ると、カードをスワイプするとどうなりましたか?
実際、カードに保存されているID(おそらく学生番号)は1つだけです。スワイプすると、プロパティシステムが情報とアカウントを確認し、「このドアに入ることができますか?」「このチキンレッグはどのアカウントにする必要がありますか」を決定します。お金を差し引くために行きますか?」
この種の操作は、フロントエンドおよびバックエンドの認証システムでは、セッションと呼ばれます。典型的なセッションログイン/認証プロセス:
ブラウザログインはアカウントパスワードを送信し、サーバーはユーザーデータベースをチェックし、ユーザーを確認します
サーバーはユーザーのログインステータスをセッションとして保存し、sessionIdを生成します
ログインインターフェイスから戻り、sessionIdをCookieに設定します
その後、ブラウザはビジネスインターフェイスを再度要求し、sessionIdがCookieとともに取得されます。
サーバーはsessionIdをチェックして、セッションを確認します
成功したら、通常どおりビジネス処理を行い、結果を返します
「セッション保存方法」明らかに、サーバーはCookieにsessionIdのみを提供し、セッションの特定のコンテンツ(ユーザー情報、セッションステータスなどを含む場合があります)を単独で保存する必要があります。保存する方法はいくつかあります。
Redis(推奨):インメモリデータベース、redis中国の公式ウェブサイト。これは、まさにsessionId-sessionDataのシーンであるkey-valueの形式で格納され、アクセスは高速です。
メモリ:変数に直接。サービスが再起動されると、それはなくなります
データベース:通常のデータベース。パフォーマンスは高くありません。
保存されたセッションデータが破棄される限り、「セッションの有効期限と破棄」は非常に簡単です。「セッションの分散問題」は通常、サーバー側のクラスターであり、ユーザー要求は負荷分散を通過しますが、必ずしもどのマシンがヒットするかはわかりません。次に、ユーザーの後続のインターフェイスによって要求されたマシンが、ユーザーがログインを要求したマシンと一致しない場合、またはログインを要求されたマシンがダウンした場合、セッションは無効になりませんか?現在、この問題にはいくつかの解決策があります。
まず、「ストレージ」の観点から、セッションは一元的に保存されます。別のRedisまたは通常のデータベースを使用する場合は、すべてのセッションをライブラリに保存できます。
2つ目は「分散」の観点からであり、同じIPからの要求はすべて、負荷分散中に同じマシンでヒットします。例としてnginxを取り上げます。これを実現するために、ip_hashを構成できます。
ただし、通常は最初の方法が使用されます。これは、2番目の方法は負荷分散を去勢することと同等であり、「ユーザーが要求したマシンがダウンしている」という問題を解決できないためです。「node.jsでのセッション処理」の前の図は非常に明確であり、サーバーはCookieとセッションへのアクセスを実現する必要があり、まだやるべきことがたくさんあります。にはnpm
、express-session-npmなどのカプセル化されたミドルウェアがすでに存在し、使用状況は投稿されません。これがその種類のCookieです。
express-session-npmは主に以下を実装します:
Cookieの読み取りおよび書き込み操作をカプセル化し、構成フィールド、暗号化方法、有効期限などを提供します。
セッションのアクセス操作をカプセル化し、セッションの保存方法(memory / redis)、保存ルールなどを構成するための構成項目を提供します。
reqにセッション属性を提供し、属性の設定/取得を制御し、Cookieとセッションアクセスに応答し、req.sessionにいくつかのメソッドを提供します。
アプリケーションスキーム:トークン
セッションのメンテナンスはサーバーに多くの問題を引き起こしました。それを保存する場所を見つける必要があり、配布の問題を考慮し、Redisクラスターのセットを単独で有効にする必要があります。もっと良い方法はありますか?
もう一度学校のことを考えました。学生証の技術が登場する前は、みんな「学生証」に頼っていました。ドアマンは学生証と私に直接顔を確認し、学生証の有効期間や学年などを確認したところ、合格しました。
振り返って考えてみると、ログインシナリオはセッションにあまり多くを保存する必要がないので、Cookieに直接パックしてみませんか?このように、サーバーはそれを保存する必要はなく、Cookieによって運ばれる「証明書」の有効性を毎回検証するだけでよく、軽量の情報も運ぶことができます。このメソッドは通常、トークンと呼ばれます。
トークンフローは次のとおりです。
ユーザーがログインし、サーバーがアカウントのパスワードを確認し、ユーザー情報を取得します
ユーザー情報とトークン構成をトークンにエンコードし、Cookieセットを介してブラウザーに送信します
その後、ユーザーはビジネスインターフェースを要求し、Cookieを介してトークンを運びます
インターフェイスはトークンの有効性を検証し、通常のビジネスインターフェイス処理を実行します
「クライアントトークンの保存方法」前のCookieで述べたように、Cookieはクライアントが資格情報を保存する唯一の方法ではありません。「ステートレス」であるため、トークンの有効期間と使用制限がトークンコンテンツに含まれ、Cookie管理機能への依存度が低くなり、クライアントはより自由に保存できます。しかし、Webアプリケーションの主流の方法は依然としてCookieに入れられており、結局のところ、心配する必要はありません。「トークンの有効期限」では、トークンの有効期間をどのように制御するのでしょうか。非常にシンプルで、「有効期限」をデータに入れて、検証時に判断するだけです。
トークンのエンコーディング
コーディングの方法は人々によって倹約されています。ノード側のcookie-sessionなどの「base64」 -npmライブラリ
名前は気にしないでください。実際にはトークンライブラリですが、express-session-npmとの一貫性の高い使用法を維持し、セッションに保存するデータをハングアップします。
デフォルトの構成では、ユーザーIDを指定すると、次のように保存されます。
ここeyJ1c2VyaWQiOiJhIn0=
では{"userid":"abb”}
、のbase64です。「改ざん防止」
問題は、ユーザーcddが
{"userid":"abb”}
base64を取得し、自分のトークンを手動で変更しeyJ1c2VyaWQiOiJhIn0=
た場合、abbのデータに直接アクセスできるかどうかです。
はい。したがって、状況によっては、トークンに機密性の高い権限が含まれている場合、トークンが改ざんされないようにする方法を見つける必要があります。解決策は、トークンに署名して、トークンが改ざんされているかどうかを識別することです。たとえば、cookie-session-npmライブラリに、次の2つの構成を追加します。
secret: 'iAmSecret',signed: true,
このように、さまざまな.sig cookieがあり、内部の値は{"userid":"abb”}
、iAmSecret
HMACSHA256クラス(System.Security.Cryptography)|MicrosoftDocsなどの暗号化アルゴリズムによって計算されます。
さて、cddは偽造できますがeyJ1c2VyaWQiOiJhIn0=
、秘密を知らないため、sigの内容を偽造することはできません。「JWT」しかし、上記のアプローチではCookieの数が増え、データ自体の形式が標準化されていないため、JSONWebトークンの概要-jwt.ioが誕生しました。
JSON Web Token(JWT)は、JSON情報を伝達する方法を定義するオープンスタンダードです。この情報は、信頼性を確保するためにデジタル署名されています。
これは、前述のデータと署名を含む成熟したトークン文字列生成スキームです。JWTトークンがどのように見えるかを見てみましょう。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyaWQiOiJhIiwiaWF0IjoxNTUxOTUxOTk4fQ.2jf3kl_uKWRkwjOP6uQRJFqMlwSABcgqqcJofFH5XCo
この一連のことはどのようにして起こったのですか?写真を見てください:
タイプ、暗号化アルゴリズムオプション、およびJWT標準データフィールドについては、RFC7519を参照できます-JSONWeb Token(JWT)ノード関連するライブラリの実装もあります:express-jwt --npm koa-jwt --npm
トークンを更新
トークン、許可保護者として、最も重要なことは「セキュリティ」です。ビジネスインターフェイスで認証に使用されるトークン。これをアクセストークンと呼びます。ビジネスの権限に敏感であるほど、盗難を防ぐためにアクセストークンの有効期間を短くする必要があります。ただし、有効期間が短すぎると、アクセストークンの有効期限が切れることがよくあります。有効期限が切れたらどうすればよいですか?1つの方法は、ユーザーが再度ログインして新しいトークンを取得できるようにすることですが、これは明らかに十分な友好的ではありません。一部のアクセストークンは、数分しか期限切れにならない可能性があることを知っておく必要があります。別の方法は、別のトークンを追加することです。これは、アクセストークン用に特別に生成されたトークンで、更新トークンと呼ばれます。
アクセストークンは、ビジネスインターフェースへのアクセスに使用されます。有効期間が十分に短いため、不正流用のリスクが少なく、リクエスト方法も緩く柔軟にすることができます。
更新トークンはアクセストークンを取得するために使用され、有効期間を長くすることができ、独立したサービスと厳格な要求方法によってセキュリティが強化されます。検証の頻度が低いため、前のセッションとして処理することもできます。
更新トークンを使用すると、いくつかの場合の要求プロセスは次のようになります。
更新トークンも期限切れになると、再度ログインすることしかできません。
セッションとトークン
セッションとトークンは非常にあいまいな境界を持つ概念です。前述のように、リフレッシュトークンは、セッションの形式で編成および維持することもできます。狭義には、セッションは「Cookieに格納され、データはサーバーに保存される」認証方式であり、トークンは「クライアント側のどこにでも保存できる」認証方式であると考えられます。データはトークンに保存されます」。セッションとトークンの比較は、基本的に「クライアントがCookieを保存する/他の場所に保存する」と「サーバーがデータを保存する/データを保存しない」の比較です。「クライアント側にCookieを保存する/他の場所に保存する」は便利で心配する必要はありませんが、問題も明らかです。
ブラウザ側ではCookieを使用できますが(実際、トークンは一般的に使用されるCookieです)、ブラウザ側にCookieがない場合はどうなりますか?
Cookieは、CSRF攻撃を受けやすいドメイン内のブラウザによって自動的に運ばれます(フロントエンドセキュリティシリーズ(2):CSRF攻撃を防ぐ方法?-Meituanテクニカルチーム)
他の場所に保存すると、Cookieを使用せずにシナリオを解決できます。パラメータなどを使用して手動で取得すると、CSRF攻撃を回避できます。「サーバーにデータが保存されています/データが保存されていません」
データストレージ:リクエストはIDを保持するだけで済みます。これにより、認証文字列の長さが大幅に短縮され、リクエストのサイズが縮小されます。
データストレージなし:サーバー側でソリューションの完全なセットと分散処理を必要としないため、ハードウェアコストが削減され、データベースチェックによる検証の遅延が回避されます。
サインイン
すでに説明したように、同じドメインのクライアント/サーバー認証システムでは、クライアントは一定期間ログイン状態を維持するための資格情報を保持します。しかし、ビジネスラインが増えると、さまざまなドメイン名でビジネスシステムが分散するようになり、「シングルサインオン」と呼ばれる「ワンログイン、ユニバーサルアクセス」の機能が必要になります。
「偽の」シングルサインオン(同じプライマリドメイン名)
たとえば、ビジネスシステムがすべて同じメインドメイン名である場合wenku.baidu.com
tieba.baidu.com
、簡単に処理できます。Cookieドメインをメインドメイン名として直接設定できますbaidu.com
。これはBaiduが行うことです。
「実際の」シングルサインオン(異なるプライマリドメイン名)
たとえば、ディディのようなトレンディな会社もdidichuxing.com
xiaojukeji.com
didiglobal.com
同じドメイン名を所有しているため、Cookieの使用は完全に避けられません。これは、「1回のログイン、全面的なユニバーサルアクセス」を実現できる場合の実際のシングルサインオンです。このシナリオでは、通常SSOと呼ばれる独立した認証サービスが必要です。「「システムBにログインせずにシステムAからログインを開始する」の完全なプロセス」
ユーザーがログイン資格情報(チケット)なしでAシステムに入ると、Aシステムは彼のSSOにジャンプします
SSOでログインしていない場合は、ssoシステムに証明書がありません(これは以前のAチケットとは異なることに注意してください)。アカウントのパスワードを入力してログインします。
SSOアカウントのパスワード検証が成功すると、インターフェイスは2つのことを実行するように戻ります。1つはssoシステムの下に資格情報を設定すること(ユーザーのログインステータスをSSOに記録すること)、もう1つはチケットを発行することです。
クライアントはチケットを取得して保存し、システムAインターフェイスを要求します
システムAはチケットを検証し、成功後にサービスリクエストを通常どおり処理します
このとき、ユーザーは初めてシステムBに入り、ログイン資格情報(チケット)を持たず、BシステムはユーザーのSSOにジャンプします。
SSOを介してログインし、システムに資格情報があります。再度ログインする必要はありません。チケットを発行するだけで済みます。
クライアントはチケットを取得して保存し、システムBのインターフェースを要求します
上記の「フルバージョン:ブラウザシナリオを検討する」のプロセスは問題ないようですが、実際、多くのAPPやその他の端末ではこれで十分です。しかし、ブラウザではうまく機能しません。ここを見て:
ブラウザの場合、SSOドメインで返されたデータは、Aにアクセスしたときに取得できるように、どのように保存する必要がありますか?ブラウザにはクロスドメインに対する厳しい制限があり、Cookie、localStorage、およびその他のメソッドにはドメイン制限があります。これには、Aドメインの下にクレデンシャルを保存する機能を提供するために、Aが必要とし、Aによってのみ提供できます。通常、これを行います。
この図では、ブラウザが現在配置されているドメイン名を色で示しています。図の灰色の背景のテキストの説明部分の変更に注意してください。
SSOドメインでは、SSOはインターフェイスを介してチケットを直接返すのではなく、コードを含むURLを介してシステムAのインターフェイスにリダイレクトします。このインターフェイスは通常、AがSSOに登録するときに合意されます。
ブラウザはAドメインにリダイレクトされ、コードを使用してAのコールバックインターフェイスにアクセスし、コールバックインターフェイスはチケットをコードと交換します
このコードはチケットとは異なります。コードは1回限りで、URLに公開されます。変更するチケットを渡すだけで、変更後は無効になります。
コールバックインターフェースがチケットを取得した後、独自のドメインにCookieを正常に設定します
以降のリクエストでは、Cookie内のチケットを解析し、確認のためにSSOに移動するだけで済みます。
Bシステムへのアクセスについても同じことが言えます
要約する
HTTPはステートレスです。フロントリクエストとバックリクエストを維持するには、フロントエンドストレージタグが必要です。
Cookieは、マーキングの完璧な方法です。HTTPヘッダーまたはjsを介して操作され、対応するセキュリティポリシーがあります。これは、ほとんどの状態管理スキームの基礎です。
セッションは状態管理スキームです。フロントエンドはCookieを介してIDを保存し、バックエンドはデータを保存しますが、バックエンドは分散された問題に対処する必要があります
トークンは別の状態管理ソリューションです。セッションと比較して、バックエンドストレージを必要としません。すべてのデータはフロントエンドに保存されるため、バックエンドが解放され、柔軟性が解放されます。
トークンのエンコーディングテクノロジー(通常はbase64に基づく)、または改ざんを防ぐための暗号化アルゴリズムの追加、jwtは成熟したエンコーディングスキームです
複雑なシステムでは、セキュリティとユーザーエクスペリエンスを満たしながら、サービストークンと更新トークンを使用してトークンを分散化できます。
セッションとトークンの比較は、「Cookieを使用するかどうか」と「Cookieがバックエンドに存在するかどうか」の比較です。
シングルサインオンでは、異なるドメインのシステムが「一度ログインして、全面的に使用する」必要があります。通常、独立したSSOシステムがログインステータスを記録してチケットを発行し、各ビジネスシステムが協力してチケットを保存および認証します。
転載元:ヘンリルル
https://juejin.cn/post/6898630134530752520
Node 社群
我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。
如果你觉得这篇内容对你有帮助,我想请你帮我2个小忙:
1. 点个「在看」,让更多人也能看到这篇文章2. 订阅官方博客 www.inode.club 让我们一起成长
点赞和在看就是最大的支持❤️