You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何验证NEAR钱包登录成功并安全绑定用户账户?

如何验证NEAR钱包登录回调的真实性?

当用户通过NEAR钱包登录后,回调接口拿到的account_id和all_keys存在被伪造的风险,你可以通过以下步骤验证登录的真实性:

核心验证逻辑

NEAR账户的公钥是存储在区块链上的,我们需要查询链上数据,确认回调中的公钥确实属于该账户,同时可加入CSRF防护进一步提升安全性。

步骤1:查询目标账户的链上公钥列表

通过NEAR的RPC节点调用view_access_key_list方法,获取指定account_id的所有有效公钥:

  • 主网RPC地址:https://rpc.mainnet.near.org
  • 测试网RPC地址:https://rpc.testnet.near.org

步骤2:对比回调中的公钥是否匹配

解析回调参数中的all_keys(JSON字符串格式的数组),检查其中的公钥是否存在于链上返回的公钥列表中。只要有一个公钥匹配,就说明用户确实拥有该账户的控制权。

步骤3:加入CSRF防护(可选但推荐)

发起登录请求时生成随机state值,存储到用户会话中,同时将state传入requestSignIn的successUrl。回调时校验state是否与会话中的值一致,防止跨站请求伪造。

修改后的API代码示例

import { string } from 'yup';
import fetch from 'node-fetch'; // 需安装node-fetch或使用其他HTTP库

import { getApplicationUrl } from '@/config/urls';
import withHandler from '@/server/api/withHandler';
import { getUser } from '@/server/user';

const REDIRECT_URL = `${getApplicationUrl()}/dashboard/profile?tab=web3`;
// 根据环境选择RPC地址
const NEAR_RPC_URL = 'https://rpc.testnet.near.org'; // 测试网,主网替换为mainnet地址

export default withHandler({
  get: async (req, res) => {
    const { uid, account_id, all_keys, state } = req.query;

    try {
      await string().validate(uid);
      await string().validate(account_id);
      await string().validate(all_keys);
      // 若启用state验证,需添加校验
      // await string().validate(state);
    } catch (error) {
      return res.redirect(`${REDIRECT_URL}&success=0&error=invalid_params`);
    }

    // 1. CSRF state校验(启用时)
    // const sessionState = req.session.nearLoginState;
    // if (!sessionState || sessionState !== state) {
    //   return res.redirect(`${REDIRECT_URL}&success=0&error=invalid_state`);
    // }
    // delete req.session.nearLoginState;

    // 2. 获取用户信息
    const user = await getUser(uid as string);
    if (!user?.uid) {
      return res.redirect(`${REDIRECT_URL}&success=0&error=user_not_found`);
    }

    try {
      // 3. 查询链上账户的公钥列表
      const response = await fetch(NEAR_RPC_URL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          jsonrpc: '2.0',
          id: '1',
          method: 'query',
          params: {
            request_type: 'view_access_key_list',
            finality: 'final',
            account_id: account_id as string,
          },
        }),
      });

      const result = await response.json();
      if (result.error) throw new Error('获取账户公钥失败');

      // 解析回调中的公钥数组
      const callbackKeys = JSON.parse(all_keys as string);
      // 提取链上公钥
      const chainKeys = result.result.keys.map((key: any) => key.public_key);

      // 4. 检查公钥是否匹配
      const hasMatchingKey = callbackKeys.some((key: string) => chainKeys.includes(key));
      if (!hasMatchingKey) {
        return res.redirect(`${REDIRECT_URL}&success=0&error=invalid_wallet`);
      }

      // 5. 验证通过,绑定钱包到用户账户
      // 此处编写你的绑定逻辑,比如更新用户文档
      // await updateUser(uid, { nearWallet: account_id });

      return res.redirect(`${REDIRECT_URL}&success=1`);
    } catch (error) {
      console.error('NEAR验证错误:', error);
      return res.redirect(`${REDIRECT_URL}&success=0&error=verification_failed`);
    }
  },
});

客户端代码调整(启用CSRF防护时)

生成随机state并存储,修改登录请求的回调地址:

async function connectWallet(uid: string) {
  const nearConnection = await connectNear();
  const walletConnection = new WalletConnection(
    nearConnection,
    appConfig.title
  );

  // 生成随机state
  const state = crypto.randomUUID();
  // 生产环境建议用HttpOnly Cookie存储,此处示例用localStorage
  localStorage.setItem('nearLoginState', state);

  walletConnection.requestSignIn({
    methodNames: [],
    successUrl: `${getApplicationUrl()}/api/user/${uid}/connectNearWallet?success=1&state=${state}`,
    failureUrl: `${getApplicationUrl()}/dashboard/profile?tab=web3&success=0`,
  });
}

export default connectWallet;

内容的提问来源于stack exchange,提问作者Shreyas Jadhav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 09:43:11