连接Metamask后如何在PHP+JavaScript环境中生成并使用JWT Token
在Metamask连接后创建并使用JWT Token的实现方案
核心思路
单纯传递钱包地址无法证明请求者是地址的合法所有者,必须让用户对特定消息签名,后端验证签名有效性后再生成JWT Token。后续前端请求携带该Token,后端通过验证Token合法性完成身份校验。
一、前端代码修改(添加签名逻辑)
修改getAccount函数,在获取钱包地址后请求用户签名,将地址、签名、消息一同传给后端:
const ethereumButton = document.querySelector('.enableEthereumButton'); const showAccount = document.querySelector('.showAccount'); ethereumButton.addEventListener('click', () => { getAccount(); }); async function getAccount() { try { const accounts = await ethereum.request({ method: 'eth_requestAccounts' }); const account = accounts[0]; // 构造带时间戳的签名消息,防止重放攻击 const message = `验证身份签名,时间戳:${Date.now()}`; // 请求Metamask完成签名 const signature = await ethereum.request({ method: 'personal_sign', params: [ethereum.utils.hexlify(ethereum.utils.toUtf8Bytes(message)), account] }); // 提交地址、签名、消息到后端 $.ajax({ type: "POST", url: "<?php echo base_url(); ?>/admin/verify_wallet_and_generate_jwt", data: { wallet: account, signature: signature, message: message }, dataType: "json", cache: false, success: function(response){ if(response.status === 'success'){ // 存储JWT到localStorage,供后续请求使用 localStorage.setItem('metamask_jwt', response.token); window.location.replace("<?php echo base_url(); ?>profile"); } else { alert('身份验证失败:' + response.msg); } }, error: function(){ alert('请求出错,请检查网络连接'); } }); } catch (error) { console.error('操作失败:', error); alert('操作失败,请检查Metamask连接状态'); } }
二、后端PHP实现(验证签名+生成JWT)
1. 安装JWT依赖
使用Composer安装PHP生态常用的Firebase JWT库:
composer require firebase/php-jwt
2. 后端处理逻辑(以CodeIgniter框架为例)
<?php use Firebase\JWT\JWT; use Firebase\JWT\Key; class Admin extends CI_Controller { // JWT密钥(使用随机安全字符串,建议存环境变量而非硬编码) private $jwt_secret = 'your_secure_random_jwt_secret_key'; private $jwt_expire = 3600; // Token有效期1小时 public function verify_wallet_and_generate_jwt() { $wallet = $this->input->post('wallet'); $signature = $this->input->post('signature'); $message = $this->input->post('message'); if(!$wallet || !$signature || !$message){ echo json_encode([ 'status' => 'error', 'msg' => '参数缺失' ]); return; } // 验证签名:从签名和消息还原地址,与用户传入地址比对 $recovered_address = $this->recover_address_from_signature($message, $signature); if(strtolower($recovered_address) !== strtolower($wallet)){ echo json_encode([ 'status' => 'error', 'msg' => '签名验证失败' ]); return; } // 签名验证通过,生成JWT $payload = [ 'iss' => base_url(), // 签发者标识 'sub' => $wallet, // 用钱包地址作为用户唯一标识 'iat' => time(), // 签发时间 'exp' => time() + $this->jwt_expire // 过期时间 ]; $token = JWT::encode($payload, $this->jwt_secret, 'HS256'); echo json_encode([ 'status' => 'success', 'token' => $token ]); } // 从签名和消息中还原钱包地址 private function recover_address_from_signature($message, $signature) { // 处理personal_sign的默认前缀 $prefix = "\x19Ethereum Signed Message:\n" . strlen($message); $hash = hash('sha256', $prefix . $message, true); $signature = hex2bin(str_replace('0x', '', $signature)); // 提取签名的r、s、v参数 $r = substr($signature, 0, 32); $s = substr($signature, 32, 32); $v = ord(substr($signature, 64, 1)); if($v < 27) $v += 27; // 依赖secp256k1扩展还原公钥,若无扩展可使用simplito/elliptic-php库替代 $public_key = secp256k1_ecdsa_recover($hash, $r, $s, $v); // 从公钥生成钱包地址 $address = '0x' . substr(hash('keccak256', substr($public_key, 1)), 24); return $address; } // 示例:验证JWT的受保护接口 public function protected_profile() { $token = $this->input->get_request_header('Authorization'); if(!$token || strpos($token, 'Bearer ') === false){ echo json_encode(['status' => 'error', 'msg' => '未提供有效身份凭证']); return; } $token = str_replace('Bearer ', '', $token); try { $decoded = JWT::decode($token, new Key($this->jwt_secret, 'HS256')); // 从解码结果中获取钱包地址,用于业务逻辑 echo json_encode([ 'status' => 'success', 'data' => ['wallet' => $decoded->sub] ]); } catch(Exception $e){ echo json_encode(['status' => 'error', 'msg' => '凭证无效或已过期']); } } } ?>
三、前端后续请求携带JWT
发起需要身份验证的请求时,在请求头中加入JWT:
$.ajax({ type: "GET", url: "<?php echo base_url(); ?>admin/protected_profile", headers: { 'Authorization': 'Bearer ' + localStorage.getItem('metamask_jwt') }, success: function(response){ console.log('用户钱包地址:', response.data.wallet); } });
关键注意事项
- 签名验证不可省略:仅传递钱包地址存在伪造风险,必须通过签名确认地址归属。
- 密钥安全:JWT密钥需使用足够长度的随机字符串,禁止硬编码到代码中,建议存储在环境变量或加密配置文件中。
- Token有效期:设置合理的有效期,过期后需引导用户重新签名获取新Token。
- 依赖支持:PHP还原地址需
secp256k1扩展,若无法安装扩展,可使用kornrunner/keccak、simplito/elliptic-php等第三方库替代。
内容的提问来源于stack exchange,提问作者Jolly
相关产品推荐
相关产品推荐

