📢 Gate廣場 #MBG任务挑战# 發帖贏大獎活動火熱開啓!
想要瓜分1,000枚MBG?現在就來參與,展示你的洞察與實操,成爲MBG推廣達人!
💰️ 本期將評選出20位優質發帖用戶,每人可輕鬆獲得50枚MBG!
如何參與:
1️⃣ 調研MBG項目
對MBG的基本面、社區治理、發展目標、代幣經濟模型等方面進行研究,分享你對項目的深度研究。
2️⃣ 參與並分享真實體驗
參與MBG相關活動(包括CandyDrop、Launchpool或現貨交易),並曬出你的參與截圖、收益圖或實用教程。可以是收益展示、簡明易懂的新手攻略、小竅門,也可以是現貨行情點位分析,內容詳實優先。
3️⃣ 鼓勵帶新互動
如果你的帖子吸引到他人參與活動,或者有好友評論“已參與/已交易”,將大幅提升你的獲獎概率!
MBG熱門活動(帖文需附下列活動連結):
Gate第287期Launchpool:MBG — 質押ETH、MBG即可免費瓜分112,500 MBG,每小時領取獎勵!參與攻略見公告:https://www.gate.com/announcements/article/46230
Gate CandyDrop第55期:CandyDrop x MBG — 通過首次交易、交易MBG、邀請好友註冊交易即可分187,500 MBG!參與攻略見公告:https://www.gate.com/announcements
SIWE技術助力Dapp提升用戶身分驗證
SIWE:提升 Dapp 功能的關鍵技術
SIWE(Sign-In with Ethereum)是一種在以太坊上驗證用戶身分的方法,類似於發起錢包交易,證明用戶對錢包擁有控制權。目前的身分驗證方式非常簡單,只需在錢包插件中對信息進行籤名即可,大多數常見錢包插件都已支持。
本文主要討論以太坊上的籤名場景,不涉及其他區塊鏈如 Solana、SUI 等。
是否需要 SIWE
如果你的 Dapp 具有以下需求,可以考慮使用 SIWE:
但如果你的 Dapp 主要是查詢功能,比如類似 etherscan 的應用,可以不使用 SIWE。
你可能會疑惑,在 Dapp 上通過錢包連接後,不就已經證明了錢包所有權嗎?這個說法部分正確。對前端而言,錢包連接確實表明了身分,但對於需要後端支持的接口調用,僅傳遞地址是不夠的,因爲地址是公開信息,任何人都可以"借用"。
SIWE 的原理和流程
SIWE 流程可概括爲三個步驟:連接錢包 - 籤名 - 獲取身分標識。讓我們詳細了解這三個步驟。
連接錢包
連接錢包是常見的 Web3 操作,通過錢包插件可以在 Dapp 中連接錢包。
籤名
SIWE 中的籤名步驟包括獲取 Nonce 值、錢包籤名和後端籤名校驗。
獲取 Nonce 值需要調用後端接口。後端收到請求後,會生成隨機 Nonce 值並與當前地址關聯,爲後續籤名做準備。
前端獲取 Nonce 值後,需要構建籤名內容,包括 Nonce 值、域名、鏈 ID、籤名內容等,通常使用錢包提供的籤名方法進行籤名。
構建完籤名後,將其發送給後端。
獲取身分標識
後端校驗籤名通過後,會返回用戶身分標識,如 JWT。前端後續請求時帶上對應地址和身分標識,即可證明錢包所有權。
實踐
現有許多組件和庫支持快速接入錢包連接和 SIWE。我們的目標是讓 Dapp 能返回 JWT 用於用戶身分校驗。注意,此 demo 僅用於介紹 SIWE 基本流程,生產環境使用可能存在安全問題。
準備工作
本文使用 Next.js 開發應用,需要 Node.js 環境。使用 Next.js 的好處是可以直接開發全棧項目,無需分爲前後端兩個項目。
安裝依賴
首先安裝 Next.js,在項目目錄中執行:
npx create-next-app@14
按提示安裝完成後,進入項目目錄並運行:
npm run dev
根據終端提示,訪問 localhost:3000 即可看到基本的 Next.js 項目。
安裝 SIWE 相關依賴
SIWE 需要登入體系,因此需要連接錢包。這裏我們使用 Ant Design Web3,因爲:
在終端執行:
npm install antd @ant-design/web3 @ant-design/web3-wagmi wagmi viem @tanstack/react-query --save
引入 Wagmi
Ant Design Web3 的 SIWE 依賴 Wagmi 庫實現。我們需要在 layout.tsx 中引入相關 Provider,使整個項目都可以使用 Wagmi 提供的 Hooks。
首先定義 WagmiProvider 配置:
javascript "use client"; import { getNonce, verifyMessage } from "@/app/api"; import { Mainnet, MetaMask, OkxWallet, TokenPocket, WagmiWeb3ConfigProvider, WalletConnect, } from "@ant-design/web3-wagmi"; import { QueryClient } from "@tanstack/react-query"; import React from "react"; import { createSiweMessage } from "viem/siwe"; import { http } from "wagmi"; import { JwtProvider } from "./JwtProvider";
const YOUR_WALLET_CONNECT_PROJECT_ID = "c07c0051c2055890eade3556618e38a6"; const queryClient = new QueryClient();
const WagmiProvider: React.FC = ({ children }) => { const [jwt, setJwt] = React.useState(null);
return ( <wagmiweb3configprovider siweconfig="{{" getnonce:="" async="" (address)=""> (await getNonce(address)).data, createMessage: (props) => { return createSiweMessage({ ...props, statement: "Ant Design Web3" }); }, verifyMessage: async (message, signature) => { const jwt = (await verifyMessage(message, signature)).data; setJwt(jwt); return !!jwt; }, }} chains={[Mainnet]} transports={{ [Mainnet.id]: http(), }} walletConnect={{ projectId: YOUR_WALLET_CONNECT_PROJECT_ID, }} wallets={[ MetaMask(), WalletConnect(), TokenPocket({ group: "Popular", }), OkxWallet(), ]} queryClient={queryClient} > {children} ); };
export default WagmiProvider;
然後添加連接錢包按鈕,這樣就在前端添加了連接入口。至此已經接入了 SIWE,步驟非常簡單。
接下來定義連接按鈕,實現連接錢包和籤名:
javascript "use client"; import type { Account } from "@ant-design/web3"; import { ConnectButton, Connector } from "@ant-design/web3"; import { Flex, Space } from "antd"; import React from "react"; import { JwtProvider } from "./JwtProvider";
export default function App() { const jwt = React.useContext(JwtProvider);
const renderSignBtnText = ( defaultDom: React.ReactNode, account?: Account ) => { const { address } = account ?? {}; const ellipsisAddress = address ? ${address.slice(0, 6)}...${address.slice(-6)} : ""; return Sign in as ${ellipsisAddress}; };
return ( <>
這樣就實現了一個最簡單的 SIWE 登入框架。
接口實現
SIWE 需要一些接口來幫助後端校驗用戶身分。現在我們來簡單實現一下。
Nonce
Nonce 用於讓錢包每次籤名時生成的內容變化,提高籤名可靠性。Nonce 生成需要與用戶傳入的 address 關聯,提高驗證準確性。
Nonce 實現非常直接,首先生成隨機字符串(由字母和數字組成),然後將 nonce 和 address 建立聯繫:
javascript import { randomBytes } from "crypto"; import { addressMap } from "../cache";
export async function GET(request: Request) { const { searchParams } = new URL(request.url); const address = searchParams.get("address");
if (!address) { throw new Error("Invalid address"); } const nonce = randomBytes(16).toString("hex"); addressMap.set(address, nonce); return Response.json({ data: nonce, }); }
signMessage
signMessage 用於籤名內容,這部分功能通常由錢包插件完成,我們一般不需要配置,只需指定方法即可。本 demo 中使用 Wagmi 的籤名方法。
verifyMessage
用戶籤名後,需將籤名前內容和籤名一同發給後端校驗。後端從籤名中解析出對應內容進行比較,一致則表示驗證通過。
此外,還需對籤名內容進行安全性校驗,如籤名內容中的 Nonce 值是否與派發給用戶的一致等。驗證通過後,需返回用戶 JWT 用於後續權限校驗,示例代碼如下:
javascript import { createPublicClient, http } from "viem"; import { mainnet } from "viem/chains"; import jwt from "jsonwebtoken"; import { parseSiweMessage } from "viem/siwe"; import { addressMap } from "../cache";
const JWT_SECRET = "your-secret-key"; // 請使用更安全的密鑰,並添加對應的過期校驗等
const publicClient = createPublicClient({ chain: mainnet, transport: http(), });
export async function POST(request: Request) { const { signature, message } = await request.json();
const { nonce, address = "0x" } = parseSiweMessage(message); console.log("nonce", nonce, address, addressMap);
// 校驗 nonce 值是否一致 if (!nonce || nonce !== addressMap.get(address)) { throw new Error("Invalid nonce"); }
// 校驗籤名內容 const valid = await publicClient.verifySiweMessage({ message, address, signature, });
if (!valid) { throw new Error("Invalid signature"); }
// 生成 jwt 並返回 const token = jwt.sign({ address }, JWT_SECRET, { expiresIn: "1h" }); return Response.json({ data: token, }); }
至此,一個基本實現 SIWE 登入的 Dapp 就開發完成了。
優化建議
使用默認 RPC 節點進行 SIWE 登入驗證可能需要近 30 秒,因此強烈建議使用專門的節點服務來提升接口響應時間。本文使用 ZAN 的節點服務,可以從 ZAN 節點服務控制臺獲取對應 RPC 連接。
獲取以太坊主網的 HTTPS RPC 連接後,在代碼中替換 publicClient 的默認 RPC:
javascript const publicClient = createPublicClient({ chain: mainnet, transport: http('), //獲取到的 ZAN 節點服務 RPC });
替換後,驗證時間將顯著減少,接口速度明顯加快。