Web3 身份与 SIWE:签名登录、去中心化标识与账户抽象

Web3 身份的核心是『用私钥签名证明你是谁』:从 SIWE(Sign-In With Ethereum)标准签名登录、EIP-712 结构化签名的安全细节,到 DID(去中心化标识符)与 Verifiable Credential 可验证凭证、ENS 域名即身份,再到账户抽象(ERC-4337)与社交恢复。覆盖签名验证流程、session 管理、安全威胁(钓鱼/重放/中间人)与工程落地。

导语:链上的"你是谁",始于一次签名

Web2 的身份是"账号 + 密码 + 手机验证码"。Web3 的身份回到原初:你拥有私钥,就能证明你是那个地址。但"证明"不是把私钥交出去,而是签名——用密码学零知识地证明"我掌握这把钥匙"。

SIWE(Sign-In With Ethereum)把签名登录标准化:一条规范的消息 + 一个签名 = 一次安全的登录凭证。它解决了"钱包登录 DApp"时最常见的三个坑:重放攻击、钓鱼签名、session 失效。

一句话总结:Web3 身份 = 私钥证明 + 标准化消息 + 会话管理;SIWE 是把"钱包连接"升级为"密码学验证的登录"的桥梁。


1. 从"连接钱包"到"签名登录"

1.1 连接钱包 ≠ 登录

旧做法:DApp 请求钱包连接(eth_requestAccounts)
  → 前端拿到地址 → 认为"用户已登录"
  ❌ 地址是公开的,任何人都能伪造"是这个地址"
  ❌ 没有密码学证据,服务端无法验证身份
  ❌ 无法跟踪 session、无法吊销

正确做法:签名登录
  → 服务端生成一条消息(含随机 nonce 与过期时间)
  → 用户用私钥签名
  → 服务端用地址公钥验签 → 确认"地址持有人"
  → 发放 session token

1.2 一次 SIWE 流程

1. DApp 请求 → 后端生成 SIWE 消息
   (含 domain、address、uri、nonce、issued-at、expiration)
2. 前端把消息给钱包签名(personal_sign)
3. 后端校验:地址与签名匹配 + nonce 有效 + 未过期 + domain 匹配
4. 校验通过 → 建立 session(签发 JWT / 存会话)
5. 用户后续请求携带 session token

2. SIWE 消息规范

SIWE(EIP-4361)定义了消息的精确格式,防止跨站/跨应用重放:

<domain> wants you to sign in with your Ethereum account:
<address>

I accept the Terms of Use

URI: <uri>
Version: 1
Chain ID: <chain_id>
Nonce: <random_string>
Issued At: <ISO8601>
Expiration Time: <ISO8601>      (可选)
Not Before: <ISO8601>           (可选)
Request ID: <request_id>        (可选)
Resources:                       (可选)
- <resource_url>

2.1 关键字段的安全作用

字段防什么攻击
domain防跨站重放(Phishing)——签名只能用于该域名
address明确签署主体
nonce防重放(Replay)——一次性随机数
Issued At会话新鲜度
Expiration Time会话时效
URI绑定具体应用

2.2 签名与验签

// 前端:用钱包签名
import { SiweMessage } from "siwe";

const siwe = new SiweMessage({
  domain: window.location.host,
  address: wallet.address,
  uri: window.location.origin,
  version: "1",
  chainId: 1,
  nonce: await getNonce(),  // 从后端获取
  issuedAt: new Date().toISOString(),
});
const message = siwe.prepareMessage();
const signature = await wallet.signMessage(message);
// 后端:验证签名
import { SiweMessage } from "siwe";

const siwe = new SiweMessage(message);
const verified = await siwe.verify({ signature, nonce });
if (verified.success) {
  // 确认真实地址,创建 session
  const address = verified.data.address;
  // 校验 domain 与当前请求 host 一致
}

一句话总结:SIWE 是把"签名登录"规范化的元协议——domain + nonce + 时间戳三件套,堵死了跨站重放与钓鱼签名两大漏洞。


3. Session 管理与安全细节

3.1 Session 生命周期

登录成功 → 签发短期 access token(如 JWT 15min)
        → 签发 refresh token(如 7 天,HttpOnly Cookie)
        → 过期 → 用 refresh token 续签(需重新验签?)
        → 退出 → 吊销 session

重要:签名只在登录/续签时发生,不每次请求都签

3.2 关键安全决策

决策推荐
token 存储服务端 session 或 HttpOnly Cookie(防 XSS)
签名消息永远让用户看清内容(EIP-712 可读)
nonce 管理一次性 + 短期有效(5 分钟)
domain 校验服务端强制匹配请求 Host
重放防护服务端记录已用 nonce
验签错误统一返回,不泄露细节

3.3 服务端验签伪代码

def verify_login(message, signature, expected_domain):
    # 1. 解析并校验格式
    siwe = parse_siwe(message)
    assert siwe.domain == expected_domain, "domain mismatch"
    # 2. 校验 nonce 未使用且未过期
    assert nonce_store.is_valid(siwe.nonce), "invalid nonce"
    # 3. 校验时间窗
    assert now_between(siwe.issued_at, siwe.expiration_time)
    # 4. 密码学验签
    addr = recover_address(message, signature)
    assert addr == siwe.address, "address mismatch"
    # 5. 消耗 nonce,防止重放
    nonce_store.consume(siwe.nonce)
    return create_session(addr)

一句话总结:签名是登录的"第一步",session 才是日常的"通行证"——把签名频率降到登录/续签,用 HttpOnly cookie + nonce 一次性把安全边界立住。


4. 身份层:DID 与可验证凭证

4.1 DID(去中心化标识符)

DID 结构:did:method:identifier
  例:did:ethr:0x...(以太坊地址作为身份)
      did:web:example.com(域名绑定身份)
      did:key:z6Mk...(公钥直接作为身份)

DID 的特点:
  - 自控(Self-sovereign):不由单一机构签发
  - 可解析(Resolution):从 DID → 元数据(公钥、服务端点)
  - 可移植:跨应用复用同一身份

4.2 Verifiable Credential(可验证凭证)

场景:学历、KYC、会员、票务
  签发者(Issuer)→ 签发 VC(签名)
  持有人(Holder)→ 存储并选择性披露
  验证者(Verifier)→ 验签 + 校验凭证

选择性披露(Zero-Knowledge):
  只证明"我年龄>18"而不泄露生日 → ZK 技术实现

4.3 身份系统分层

应用身份(SIWE session)→ 授权访问应用
身份标识(DID / ENS)  → 统一的名字/身份
凭证(VC)            → 可验证的属性证明

5. ENS:从地址到名字

ENS(Ethereum Name Service)把 0xAbc... 变成 alice.eth:

ENS 工作原理:
  namehash:递归哈希解析域名树
  resolver:将名字解析到地址/内容(合约)
  注册:ENS 根合约管理子域名

价值:
  - 可读地址:钱包收款、身份展示
  - 子域名:组织身份(team.eth)
  - 解析器:绑定邮箱、头像、社交资料(ENS Avatar)

5.1 ENS 与 SIWE 结合

// 用 ENS 名字作为用户界面,底层仍是地址
const name = await ens.lookupAddress(wallet.address);
// 前端显示 alice.eth,验签仍针对底层地址

6. 账户抽象:身份即合约

ERC-4337 把"身份"从 EOA(外部账户)升级为智能账户——身份规则可编程:

6.1 传统 vs 智能账户

维度EOAERC-4337 智能账户
恢复私钥丢失=丢失可社交恢复(多签名)
权限全有或全无可设置限额/角色
批处理逐个交易UserOperation 批量
付费必须 ETH可代付(Gasless)
登录仅私钥签名支持 WebAuthn、邮箱

6.2 一个智能账户的登录

用户通过任意方式(如 WebAuthn)发起登录意图
  → 构造 UserOperation(含验证 + 执行数据)
  → Bundler 打包 → EntryPoint 合约执行
  → 智能账户合约验证权限 → 执行操作

6.3 身份演进

EOA(一个私钥=一个身份)
  ↓ 账户抽象
智能账户(可恢复、可编程、可多因子)
  ↓ 未来
链上身份 + AI 代理(Agent 持有权限委托)

7. 常见攻击与防护

攻击手法防护
钓鱼签名诱导签任意数据SIWE + EIP-712 可读
重放复用旧签名nonce + 时间窗
中间人篡改消息验签校验全字段
恶意 DApp请求超额权限最小权限、每请求授权
地址混淆相似地址ENS + 显示警告
重入交易内多次调用状态机 + 权限校验

7.1 最小权限原则

签名策略:
  交易签名:一次性、限金额、EIP-712
  授权签名:限 spender、限额度、可撤销
  登录签名:仅登录意图,不包含授权

8. 工程落地:一个完整的 SIWE 登录系统

8.1 目录结构

frontend/
  ├─ useSiwe.ts        # 封装登录逻辑
  └─ components/       # 连接钱包 + 签名按钮
backend/
  ├─ siwe/verify.ts    # 验签路由
  ├─ siwe/nonce.ts     # nonce 签发
  └─ session.ts        # session 管理

8.2 完整登录钩子

async function handleLogin(provider: Eip1193Provider, backend: Backend) {
  // 1. 连接钱包
  const wallet = new BrowserProvider(provider);
  const signer = await wallet.getSigner();
  const address = await signer.getAddress();

  // 2. 获取 nonce
  const nonce = await backend.getNonce();

  // 3. 构造并签署 SIWE 消息
  const siwe = new SiweMessage({
    domain: window.location.host,
    address,
    uri: window.location.origin,
    version: "1",
    chainId: await wallet.getNetwork().then((n) => n.chainId),
    nonce,
    issuedAt: new Date().toISOString(),
  });
  const signature = await signer.signMessage(siwe.prepareMessage());

  // 4. 提交后端验签
  const session = await backend.verify({ message: siwe, signature });
  return session;  // 后续请求带 session token
}

8.3 服务端路由

// 后端:nonce 签发
app.get("/api/nonce", (req, res) => {
  const nonce = generateRandomNonce();
  nonceStore.set(nonce, { ttl: 5 * 60_000 });
  res.json({ nonce });
});

// 后端:验签 + 建 session
app.post("/api/verify", async (req, res) => {
  const { message, signature } = req.body;
  const siwe = new SiweMessage(message);
  const { data, success } = await siwe.verify({
    signature,
    domain: req.headers.host,  // 强制域名校验
  });
  if (!success) return res.status(401).json({ error: "invalid" });
  const session = await createSession(data.address);
  res.cookie("session", session.token, { httpOnly: true, sameSite: "lax" });
  res.json({ ok: true });
});

9. 身份的未来:跨链与自主权

- 跨链身份:一个 DID 映射多链地址(DID 解析器聚合)
- 自主权:用户掌握身份数据(而非平台)
- 身份聚合:社交 + 链上 + 凭证的合成身份
- AI 代理:Agent 用授权密钥代表用户操作(能力边界可编程)

9.1 能力边界示例

// 智能账户中的委托权限
mapping(address => Permission) public permissions;

struct Permission {
    uint256 dailyLimit;
    address[] allowedApps;
    bool canTransfer;
}

10. 总结

  1. 身份本质:私钥签名证明"你是那个地址"
  2. SIWE:domain + nonce + 时间窗,标准化安全登录
  3. Session:签名仅一次,日常走 token(HttpOnly)
  4. DID / VC:从地址到可验证身份的扩展
  5. ENS:可读名字包装底层地址
  6. 账户抽象:身份从 EOA 演进为可编程合约
  7. 防护:最小权限 + 可读签名 + 全字段校验

延伸阅读:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「blockchain」更多文章

  1. 链上数据索引:The Graph、Subgraph 与数据查询架构
  2. 钱包与 HD 分层确定性钱包:助记词、派生路径与签名流程
  3. 稳定币与 DEX/AMM 机制:锚定设计、流动性池与无常损失