5月28日 00:00

如何实现 DApp 的用户身份认证?有哪些常见方式?

DApp 用户身份认证有哪些方式?

DApp 的身份认证与传统 Web 应用完全不同——没有用户名密码,没有 Cookie Session,取而代之的是钱包签名、链上验证和去中心化标识。面试中常从"钱包连接"切入,逐步追问 SIWE、DID、ZKP 等进阶方案。

钱包连接:最基础的认证方式

钱包连接是 DApp 认证的起点。用户通过 MetaMask 等钱包授权 DApp 读取其以太坊地址,地址即为身份标识。

核心流程:调用 eth_requestAccounts 获取地址 → 验证地址格式 → 以地址作为用户唯一标识。

javascript
async function connectWallet() { if (!window.ethereum) { throw new Error("请安装 MetaMask"); } const accounts = await window.ethereum.request({ method: "eth_requestAccounts" }); const address = accounts[0].toLowerCase(); if (!/^0x[a-f0-9]{40}$/i.test(address)) { throw new Error("地址格式无效"); } return address; }

局限:仅能证明用户拥有该地址,无法证明"是谁在操作"——同一地址可能被多人控制,也无法区分不同会话。这正是 SIWE 要解决的问题。

SIWE(Sign-In with Ethereum):当前主流认证标准

SIWE 是 ERC-4361 定义的标准协议,通过钱包签名一条结构化消息来证明身份,相当于 Web3 的"登录"。

认证流程

  1. 后端生成随机 nonce,返回给前端
  2. 前端构造 EIP-4361 格式消息,请求钱包签名
  3. 后端通过 ecrecover 从签名恢复出签名者地址
  4. 验证 nonce、过期时间、域名等字段,通过后签发 JWT Session
javascript
// 前端:构造 SIWE 消息并签名 import { SiweMessage } from "siwe"; async function signInWithEthereum() { // 1. 从后端获取 nonce const nonce = await fetch("/api/nonce").then(r => r.text()); // 2. 构造 EIP-4361 标准消息 const message = new SiweMessage({ domain: window.location.host, address: await getAddress(), statement: "Sign in to DApp", uri: window.location.origin, version: "1", chainId: 1, nonce, issuedAt: new Date().toISOString(), expirationTime: new Date(Date.now() + 600000).toISOString() }); // 3. 请求钱包签名 const signature = await window.ethereum.request({ method: "personal_sign", params: [message.prepareMessage(), await getAddress()] }); // 4. 发送到后端验证 const res = await fetch("/api/verify", { method: "POST", body: JSON.stringify({ message, signature }) }); return res.ok; }
javascript
// 后端:验证签名 const { SiweMessage } = require("siwe"); async function verifySiwe(message, signature) { const siweMessage = new SiweMessage(message); const result = await siweMessage.verify({ signature }); if (!result.success) throw new Error("签名验证失败"); // 检查 nonce 防重放、检查域名防钓鱼 if (result.data.nonce !== storedNonce) throw new Error("Nonce 不匹配"); return result.data.address; // 返回已验证的地址 }

为什么 SIWE 比单纯钱包连接更安全:nonce 防重放攻击,域名绑定防钓鱼,过期时间限制会话有效期,签名操作零 Gas 费。

去中心化身份(DID)与可验证凭证(VC)

DID 是 W3C 标准化的去中心化标识符,格式为 did:method:identifier(如 did:ethr:0x1234...)。与传统地址标识不同,DID 将公钥、服务端点等元数据记录在链上 DID 文档中,支持密钥轮换和多设备管理。

DID 与 VC 的协作模式

  • DID:用户的去中心化标识,链上存储 DID 文档
  • VC(Verifiable Credential):由可信机构签发的凭证(如 KYC 认证、学历证明),以 DID 为主体
  • 验证流程:持有者出示 VC → 验证者解析颁发者 DID → 链上验证签名 → 确认凭证有效性
javascript
// 使用 did-jwt 库创建和验证 DID 相关凭证 import { createVerifiableCredentialJwt, verifyCredential } from "did-jwt-vc"; import { Resolver } from "did-resolver"; import { getResolver } from "ethr-did-resolver"; const resolver = new Resolver(getResolver({ rpcUrl: "https://mainnet.infura.io/v3/YOUR_KEY" })); // 验证者:验证 VC 的签名和有效期 async function verifyVC(jwt) { const verified = await verifyCredential(jwt, resolver); if (!verified.verified) throw new Error("VC 验证失败"); return verified.payload; // 返回凭证内容 }

DID 的优势:用户自主控制身份数据,可跨 DApp 复用,无需重复注册。劣势:生态碎片化(多种 DID 方法并存),链上解析延迟较高,密钥管理对普通用户门槛大。

零知识证明在身份认证中的应用

零知识证明允许用户证明某个声明(如"我已满 18 岁")而不暴露具体数据(如出生日期),适用于高隐私场景。

典型场景:KYC 合规验证——用户向 DApp 证明自己通过了 KYC,但不暴露姓名、身份证号等敏感信息。

实现路径(以 zk-SNARK 为例):

  1. 可信机构对用户身份数据生成承诺(commitment),签发 VC
  2. 用户在本地生成 ZK 证明:证明"持有某 VC 且满足条件(如 age ≥ 18)"
  3. DApp 验证链上证明,确认声明有效,不接触原始数据
solidity
// 简化的链上 ZK 验证器(使用 Groth16) contract IdentityVerifier { function verifyProof( uint[2] memory a, uint[2][2] memory b, uint[2] memory c, uint[1] memory input // public input: 如 age_threshold 的 hash ) public returns (bool) { return IVerifier(verifier).verifyProof(a, b, c, input); } }

当前局限:证明生成耗时较长(数秒),Gas 费用高,开发门槛大。适合对隐私要求极高的金融和医疗场景,不建议在普通 DApp 中滥用。

方案对比与选型建议

方案去中心化程度实现难度隐私保护适用场景
钱包连接基础 DApp 入口
SIWE主流 DApp 登录
DID + VC跨应用身份复用、合规
ZKP 证明很高极高隐私敏感型 DeFi、KYC

选型原则:从钱包连接起步,引入 SIWE 做会话管理,需要跨应用身份互通时接入 DID,仅在强隐私需求时引入 ZKP。不要一开始就追求最去中心化的方案——用户体验和开发成本同样重要。

面试追问与要点

Q: SIWE 和单纯钱包签名有什么区别? 单纯钱包签名没有标准格式,消息内容、域名、过期时间全靠自定义,容易遭受重放和钓鱼攻击。SIWE 定义了 EIP-4361 标准消息格式,包含 nonce、domain、expiration-time 等字段,后端可系统性校验,安全性远高于自定义签名。

Q: DID 如何解决"跨 DApp 身份复用"问题? DID 文档存储在链上,任何 DApp 都可通过解析 DID 获取用户的公钥和服务端点。用户在一个 DApp 中通过 DID 注册后,其他 DApp 只需解析同一 DID 即可识别用户,无需重复提交信息。配合 VC,用户还可选择性披露凭证属性,实现最小化信息披露。

Q: ZKP 身份认证的性能瓶颈在哪? 主要瓶颈在证明生成阶段:Groth16 证明生成需要数秒到数十秒,且依赖可信设置(trusted setup)。验证阶段 Gas 费较高,一笔 Groth16 验证约 20-30 万 Gas。解决方案包括使用递归证明压缩、链下聚合验证,以及等待 ZK 硬件加速方案成熟。

标签:Web3