該貼文系列已在 NgateSystems.com 上建立索引。您還可以在那裡找到超級有用的關鍵字搜尋工具。
最後評論:24 年 11 月
Post 3.3 帶來了一些壞消息 - 用於用戶端提供有關登入使用者資訊的 Firestore 驗證物件在伺服器端不可用。這會產生以下後果:
伺服器端資料庫程式碼必須使用 Firestore Admin API。這是因為 Firestore Client API 程式碼使呼叫遵循資料庫“規則”,當身份驗證不可用時,引用身份驗證會失敗。相比之下,管理 API 呼叫並不關心資料庫規則。如果您放棄了規則,客戶端API 呼叫將在伺服器端工作,但這將使您的資料庫容易受到網路攻擊(自從您開始使用本機VSCode 終端以來,您一直在使用即時Firestore 資料庫- 想想看) .
使用從 auth 派生的 userName 和 userEmail 等資料項目的伺服器端程式碼必須找到另一種方式來取得此資訊。
這篇文章描述如何克服這些問題來產生一個在伺服器端安全、高效運行的高效能 Web 應用程式。
如果您已經習慣了客戶端呼叫簽名,那麼切換到 Firestore Admin API 的要求會很麻煩。但你很快就會習慣這一點,所以它不會對你造成很大的阻礙。
然而,取得使用者資料是另一回事。對於許多應用程式來說,存取使用者屬性(例如 uId)對其設計至關重要。例如,網路應用程式可能需要確保用戶只能看到他們的自己的資料。不幸的是,安排這個是相當困難的。讓我們開始吧:
看看 <script> 中的以下程式碼: products-maintenance-sv/ page.svelte 「規則友善」products-maintenance-rf 程式碼的「伺服器」版本的部分。這使用 getIdToken() 存取使用者的 Firebase 驗證會話並建立 idToken<br> </script>
// src/routes/products-maintenance-sv/+page.svelte <script> import { auth } from "$lib/utilities/firebase-client"; import { onMount } from "svelte"; import { goto } from "$app/navigation"; onMount(async () => { if (!auth.currentUser) { // Redirect to login if not authenticated, with a redirect parameter goto("/login?redirect=/products-maintenance-sv"); return; } try { // Fetch the ID token directly const idToken = await auth.currentUser.getIdToken(); window.alert("idToken:" + JSON.stringify(idToken)); } catch (error) { window.alert("Error retrieving ID token:", error); } }); </script>
您之前在 products-maintenance-rf/ page.svelte 中看到了 onMount() 安排,它用於確保使用者登入。現在它也用於透過呼叫非同步身份驗證來取得 idToken 變數。 currentUser.getIdToken()。
建立一個新的 src/routes/products-maintenance-sv 資料夾,並將上面列出的程式碼貼到其中的新 page.svelte 檔案中。 現在嘗試在開發伺服器中執行它:http://localhost:5173/products-maintenance-sv。登入後(使用最後在[Post 3.4](https://ngatelive.nw.r.appspot.com/redirect?post=3.4 中看到的/login/ page.svelte 版本),您應該會看到顯示的idToken在警報訊息中。
Firebase ID 令牌是 JSON Web 令牌 (JWT)。 JSON 位元意味著它是使用“Javascript 物件表示法”編碼為字串的物件(如果這是您第一次看到“JSON”,您可能會發現向 chatGPT 詢問背景資訊很有用)。 JSON 廣泛用於需要將 Javascript 物件作為字串傳遞的地方。 JWT JSON 包含您可能需要了解的所有關於使用者的資訊。我將在本文後面向您展示如何提取這些資訊 - 這並不復雜。2.2 將idToken傳遞給伺服器
這種安排很複雜,但被認為是安全的。 從您的角度來看,作為 IT 學生,它也很有趣且具有教育意義,因為它可以深入了解網頁設計的內部結構。
客戶端 Javascript 程式可以輕鬆設定包含 JWT 的 「常規」 cookie,但出於安全原因,您非常不希望這樣做。如果你能做到這一點,那麼任何人都可以。另一方面,伺服器端 page.server.js 檔案可以使用 set-cookie 呼叫在客戶端瀏覽器中設定 "http-only" cookie。這是一個例子:
// src/routes/products-maintenance-sv/+page.svelte <script> import { auth } from "$lib/utilities/firebase-client"; import { onMount } from "svelte"; import { goto } from "$app/navigation"; onMount(async () => { if (!auth.currentUser) { // Redirect to login if not authenticated, with a redirect parameter goto("/login?redirect=/products-maintenance-sv"); return; } try { // Fetch the ID token directly const idToken = await auth.currentUser.getIdToken(); window.alert("idToken:" + JSON.stringify(idToken)); } catch (error) { window.alert("Error retrieving ID token:", error); } }); </script>
上面的 httpOnly: true 設定意味著,儘管 cookie 保存在客戶端,但它無法從 Javascript 存取。 這樣您就可以確保您在此處設定的值不會被竄改。
您現在應該問的問題是「當伺服器端 page.server.js 檔案不知道 idToken 時,如何啟動 Set-Cookie 指令來設定 idToken?」 。
歡迎使用 Svelte server.js 檔案。這是伺服器端程式碼,可以使用 Javascript fetch 指令從客戶端程式碼呼叫。這樣的伺服器端程式碼稱為「端點」。取得命令是 JavaScript 的本機方法,用於向基於 Web 的「端點」提交請求。該命令使您能夠在請求中包含數據,因此這就是您在伺服器上獲取 idToken 值的方式。這是一個例子:
// Set a secure, HTTP-only cookie with the `idToken` token const headers = { 'Set-Cookie': cookie.serialize('idToken', idToken, { httpOnly: true }) }; let response = new Response('Set cookie from server', { status: 200, headers, body: { message: 'Cookie set successfully' } // Optional message }); return response;
以下是接收者 server.js 檔案如何檢索此內容並提取其 idToken。
// client-side +page.svelte code const idToken = await user.getIdToken(); // Send token to the server to set the cookie fetch("/api/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ idToken }), });
您可能在想「為什麼這段程式碼使用「fetch」指令來「傳送」某些東西?」但你就在那裡。 「Fetch」被設計為一個多功能 API,用於發出許多不同類型的 HTTP 請求。如果您想了解一些背景知識,請向 chatGPT 索取教學。請參閱一些範例。
現在的建議是讓您的登入頁面負責進行設定瀏覽器僅http cookie的server.js呼叫。設定 cookie 後,它將自動加入瀏覽器進行的每個 HTTP 呼叫中,直到過期。
要啟動此操作,請為登入頁面的以下login-and-set-cookie/ page.svelte 版本及其隨附的api/set-cookie/ server.js 端點建立新資料夾和檔案:
// server-side +server.js code export async function POST({ request }) { const { idToken } = await request.json(); }
請注意,為了使用 api/set-cookie/ server.js,您首先需要安裝 npm "cookie" 函式庫。此程式庫有助於建立格式正確的 cookie,以便包含在 HTTP 回應標頭中。
// src/routes/login-and-set-cookie/+page.svelte <script> import { onMount } from "svelte"; import { auth, app } from "$lib/utilities/firebase-client"; import { goto } from "$app/navigation"; // SvelteKit's navigation for redirection import { signInWithEmailAndPassword } from "firebase/auth"; let redirect; let email = ""; let password = ""; onMount(() => { // Parse the redirectTo parameter from the current URL const urlParams = new URLSearchParams(window.location.search); redirect = urlParams.get("redirect") || "/"; }); // this code will run after a successful login. auth.onAuthStateChanged(async (user) => { if (user) { const idToken = await user.getIdToken(); console.log("In login_awith-cookie : idToken: ", idToken); // Send token to the server to set the cookie fetch("/api/set-cookie", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ idToken }), }); window.alert("In with-cookie : cookie set"); goto(redirect); } }); async function loginWithMail() { try { const result = await signInWithEmailAndPassword( auth, email, password, ); } catch (error) { window.alert("login with Mail failed" + error); } } </script> <div> <pre class="brush:php;toolbar:false">// src/routes/api/set-cookie/+server.js import admin from 'firebase-admin'; import cookie from 'cookie'; export async function POST({ request }) { const { idToken } = await request.json(); try { // Verify the token with Firebase Admin SDK const decodedToken = await admin.auth().verifyIdToken(idToken); // Use the cookie.serialize method to create a 'Set-Cookie' header for inclusion in the POST // response. This will instruct the browser to create a cookie called 'idToken' with the value of idToken // that will be incorporated in all subsequent browser communication requests to pages on this domain. const headers = { 'Set-Cookie': cookie.serialize('idToken', idToken, { httpOnly: true, // Ensures the cookie is only accessible by the web server secure: true, // Ensures the cookie is only sent over HTTPS sameSite: 'None', // Allows the cookie to be sent in cross-site requests maxAge: 60 * 60, // 1 hour (same as Firebase ID token expiry) path: '/' // Ensures the cookie is sent with every request, regardless of the path. }) }; let response = new Response('Set cookie from login', { status: 200, headers, body: { message: 'Cookie set successfully' } // Optional message }); console.log("Cookie set") return response; } catch (err) { console.error("Error in login server function: ", err); let response = new Response('Set cookie from login', { status: 401, body: { message: 'Unauthorized' } // Optional message }); return response; } };
不需要「登出並刪除 cookie」登出頁面。設定新的 cookie 將覆蓋任何同名的舊版本。
專案的服務帳戶是一個包含安全金鑰和「擁有者」資訊(例如專案的projectId)的物件。當「page.server.js」檔案執行時,嵌入其中的服務帳戶的副本將呈現給Google。如果兩者匹配,則伺服器檔案通過驗證。
以下程序:
現在按一下「建立並繼續」按鈕,然後前往「授予此服務帳戶存取項目的權限」部分。首先打開字段上的下拉式選單。這有點複雜,因為它有兩個面板。左側面板(有一個滑桿)可讓您選擇產品或服務。右側列出了該服務可用的角色。使用左側面板選擇“Firebase”服務,然後從右側面板中選擇“Admin SDK 管理員服務代理”角色。按一下“繼續”,然後按一下“完成”返回服務帳戶畫面
最後,按一下您剛剛建立的「Firebase Admin SDK Service Agent」金鑰項目右側的「三點」選單,然後選擇「管理金鑰」。點擊「新增金鑰」>建立新金鑰> JSON>建立並注意一個新檔案已出現在您的「下載」資料夾中。這是您的「服務帳戶金鑰」。現在您要做的就是將其嵌入到您的專案中。
// src/routes/products-maintenance-sv/+page.svelte <script> import { auth } from "$lib/utilities/firebase-client"; import { onMount } from "svelte"; import { goto } from "$app/navigation"; onMount(async () => { if (!auth.currentUser) { // Redirect to login if not authenticated, with a redirect parameter goto("/login?redirect=/products-maintenance-sv"); return; } try { // Fetch the ID token directly const idToken = await auth.currentUser.getIdToken(); window.alert("idToken:" + JSON.stringify(idToken)); } catch (error) { window.alert("Error retrieving ID token:", error); } }); </script>
這是前面在 3.3 篇文章中描述的保護機制的另一個實例,用於阻止您無意中洩露 Git 儲存庫中的檔案。對於 Windows 用戶,更安全的方法是建立 Windows GOOGLE_APPLICATION_CREDENTIAL 環境變數來提供關鍵參考。
要執行「伺服器登入」流程,您的 page.server.js 程式碼需要存取 Firebase 管理 API。您可以透過在專案中安裝“firebase-admin”來獲得此資訊:
// Set a secure, HTTP-only cookie with the `idToken` token const headers = { 'Set-Cookie': cookie.serialize('idToken', idToken, { httpOnly: true }) }; let response = new Response('Set cookie from server', { status: 200, headers, body: { message: 'Cookie set successfully' } // Optional message }); return response;
您現在可以使用以下命令在程式碼中建立管理員參考:
// client-side +page.svelte code const idToken = await user.getIdToken(); // Send token to the server to set the cookie fetch("/api/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ idToken }), });
請注意,此匯入的語法與您迄今為止使用的語法不同 - “admin”位元周圍沒有大括號。雖然您使用的其他庫允許您匯入命名元件,但此版本要求您從 default 管理匯出匯入整個內容。這提供了元件作為 properties,例如父管理物件的 admin.auth()、admin.firestore() 等。圖書館的設計者認為,在這種情況下這是一個更實用的安排。
使用預設匯入時,您可以將匯入的父物件稱為您喜歡的任何名稱(例如,您可以稱為 myFirebaseAdmin 而不是 admin)。將此排程與您先前建立的 lib/utilities/firebase-config 檔案的命名匯出方法進行比較
這是您最終了解使用 Firestore 管理 API 存取 Firestore 資料庫伺服器端的實質內容的地方。
首先,您使用服務帳戶金鑰「初始化」您的應用程序,從而獲得創建使用管理 API 所需的 adminDb 物件的權限(就像您需要用於客戶端 API 的 db 一樣)。然後,您需要從 cookie 中取得 idToken,並從中提取您在 Firestore 呼叫中可能需要的任何使用者內容。此時,您終於可以使用 Firestore 管理 API 來編寫這些呼叫的程式碼了。
將下面列出的程式碼複製到 src/routes/products-maintenance-sv 資料夾中的新 page.server.js 檔案中。這是產品維護程式碼的“伺服器版本”,首次出現在 Post 3.3 中。它用於展示嘗試使用 Firestore 用戶端 API 的伺服器端程式碼在其所尋址的集合受 Firestore 資料庫規則約束的情況下如何失敗。這個新版本的好處是:
// src/routes/products-maintenance-sv/+page.svelte <script> import { auth } from "$lib/utilities/firebase-client"; import { onMount } from "svelte"; import { goto } from "$app/navigation"; onMount(async () => { if (!auth.currentUser) { // Redirect to login if not authenticated, with a redirect parameter goto("/login?redirect=/products-maintenance-sv"); return; } try { // Fetch the ID token directly const idToken = await auth.currentUser.getIdToken(); window.alert("idToken:" + JSON.stringify(idToken)); } catch (error) { window.alert("Error retrieving ID token:", error); } }); </script>
注意程式碼建構 userEmail 欄位的奇怪方式
// Set a secure, HTTP-only cookie with the `idToken` token const headers = { 'Set-Cookie': cookie.serialize('idToken', idToken, { httpOnly: true }) }; let response = new Response('Set cookie from server', { status: 200, headers, body: { message: 'Cookie set successfully' } // Optional message }); return response;
verifyIdToken 方法名稱可能會讓您懷疑這是否正在嘗試再次驗證您的使用者。別擔心——事實並非如此。它只是對令牌的嵌入“簽名”進行安全檢查,以確保它沒有被篡改並且沒有過期。
verifyIdToken所建立的decodedToken是一個簡單的對象,包含經過驗證的使用者的電子郵件和使用者名稱屬性等。後續的 Firestore 程式碼沒有使用其中任何一個,但我相信您可以輕鬆想像它是如何做到這一點的。
我建議您在編寫管理 API 呼叫時再次使用「樣板」方法 - 如果需要,請使用 chatGPT 轉換 10.1 篇文章中記錄的客戶端程式碼。
現在用下面所示的程式碼取代您先前建立的 src/routes/products-maintenance-sv/ page.svelte 檔案的內容。這將為 products-maintenance-sv/ page.server.js 檔案提供客戶端前端:
// src/routes/products-maintenance-sv/ page.svelte 從“svelte”導入{onMount}; 從“$lib/utilities/firebase-client”導入{auth}; 從“$app/navigation”導入{goto}; 從“$lib/utilities/productNumberIsNumeric”導入{productNumberIsNumeric}; // 將對驗證狀態變更的檢查放在 onMount 內的 onAuthStateChanged 回呼中。這似乎 // 奇怪,但似乎是完成伺服器端活動並使 auth.currentUser 進入穩定狀態的唯一方法 // 狀態。這在客戶端“規則友好”版本上不是問題,但很快就成為問題 // 新增了帶有 actions() 的 page.server.js 檔案。 onMount(異步() => { auth.onAuthStateChanged(非同步(使用者)=> { if (!auth.currentUser) { // 如果未通過身份驗證,則重定向到登入。該參數告訴登入如何返回這裡 goto(“/login-and-set-cookie?redirect=/products-maintenance-sv”); } }); }); 讓產品編號; 讓產品詳細資訊; 讓產品編號類別=「產品編號」; 讓submitButtonClass =“submitButton”; 出口許可證表格; 腳本> 產品編號 <p>要在您的開發伺服器中執行此程序,請先使用 http://localhost:5173/logout 登出。然後執行http://localhost:5173/products-maintenance-sv。這將邀請您登入登入並設定 cookie 頁面。 </p> <p>成功登入後,您將看到熟悉的表單,邀請您建立新產品。 </p> <p>此時,登入並設定 cookie 頁面應該已在您的瀏覽器中安全地設定了 idToken cookie。當您輸入資料並提交表單時,控制權將傳遞到 products-maintenance-sv/page.server.js 中的伺服器端程式碼。它透過呈現專案中內建的服務代碼來進行自身驗證,然後從 Sveltekit 請求中的表單物件中取得標頭中的 idToken 及其輸入資料。程式碼不會對 idToken 中可用的使用者資料執行任何有用的操作,但會在 VSCode 終端機中顯示一條顯示 userEmail 值的日誌訊息。最後,Firestore 管理程式碼會將新產品新增至產品資料庫集合中。 </p> <p>您可以透過執行舊的 http://localhost:5173/products-display-rf 頁面來確認更新已成功應用。 </p> <p>請注意,提交表單後,它會顯示一條確認訊息並清除其輸入欄位。 「表單刷新」是表單提交後 Javascript 的預設操作。 </p><p>您可能想知道當http://localhost:5173/products-display-rf 頁面仍在執行Firestore <strong>客戶端</strong> API 程式碼伺服器端並在產品集合上設定Firestore 驗證規則時,它是如何工作的。不同之處在於這些規則僅適用於寫入。 products-display-rfcode 只是讀取文件。 </p> <p>在實踐中,我認為如果您擔心避免混淆並決定創建 products-display-sv 的 products-display-sv 版本,您會希望在整個過程中使用 Firestore <strong>Admin</strong> API 呼叫。但請記住,您需要先提供您的服務帳戶憑證來初始化應用程式。 </p> <h3> 三、總結 </h3> <p>這是一篇很長的文章,將會讓您的 Javascript 達到極限。如果你此時仍然和我在一起——幹得好。真的,幹得好——你表現出了非凡的毅力! </p> <p>上一篇文章介紹的「客戶端」技術使用起來很愉快,但我希望您會欣賞伺服器端安排的安全性和速度優勢。憑藉經驗,伺服器端程式碼開發將變得像客戶端工作一樣簡單自然。 </p> <p>但是還有一件事要做。儘管您現在已經開發了一個包含許多頁面的 Web 應用程序,這些頁面在您的開發伺服器中運作得很好,但這些頁面在 Web 上尚不可見。 </p> <p>下一篇文章將告訴您如何「建置」和部署」您的 Web 應用程式到 Google AppEngine 上,從而將其發布給熱切的公眾。這將是一個重要的時刻!</p> <p>我希望您仍有精力繼續閱讀並找出您需要做什麼。不是太難。 </p> <h3> 後記:當出現問題時 - 在檢查器中查看標題 </h3> <p>實際上,在本節中可能出現問題的地方可能接近無限。盡量不要過度恐慌,並密切注意專案的文件結構。很容易將正確的程式碼放入錯誤的檔案中,或將正確的檔案放入錯誤的資料夾中。此外,您可能會發現透過終止並重新啟動終端會話定期「清理地面」很有幫助。至少,這可以讓您在尋找錯誤序列的最初原因時獲得一張乾淨的紙。 </p> <p>但是,由於您現在正在使用標頭和 cookie,您也會發現了解瀏覽器的檢查器工具可以讓您直觀地了解這些內容很有用。檢查器可以<strong>向您顯示</strong>嵌入在頁面請求標頭中的cookie。 </p><p>要查看此功能的運作情況,首先請確保您已使用 https://myLiveUrl/logout 在即時系統上登出(其中 myLiveUrl 是您部署的 Web 應用程式的位址)。然後執行 products-maintenance=sv 頁面(網址為 https://https://myLiveUrl/products-maintenance-sv)。登入後,開啟「輸入新產品」表單上的檢查器,然後按一下「網路」標籤。現在顯示頁面發出的網路請求的清單。 </p> <p>現在使用網路應用程式插入新產品並注意「請求」清單是如何刷新的。這些是執行簡單更新所需的網路請求 - 一個令人驚訝的長列表!向上捲動到此清單的頂部,您應該會找到 products-maintenance-sv 頁面的條目。如果按一下此按鈕,請求清單右側的面板應顯示交易的回應和請求標頭的完整詳細資訊。下面的螢幕截圖顯示了嵌入在請求標頭中的 cookie。 </p> <p><img src="https://img.php.cn/upload/article/000/000/000/173301818045981.jpg" alt="NgSysV.A Serious Svelte InfoSys: A Client-Server Version"></p>
以上是NgSysV.A Serious Svelte InfoSys:客戶端-伺服器版本的詳細內容。更多資訊請關注PHP中文網其他相關文章!