如何验证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
相关产品推荐
相关产品推荐

