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

连接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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 04:46:48