PHP中使用JWT(JSON Web Token)而不使用第三方库。如何签名?

28

有一些用于在PHP中实现JSON Web Tokens(JWT)的库,例如php-jwt。我正在编写自己的非常简单的类,但无法弄清楚为什么即使我尝试遵循标准,在此处(http://jwt.io/)进行签名验证时,我的签名仍然无法通过验证。我已经尝试了几小时,但是卡住了,请求帮助!

我的代码很简单

//build the headers
$headers = ['alg'=>'HS256','typ'=>'JWT'];
$headers_encoded = base64url_encode(json_encode($headers));

//build the payload
$payload = ['sub'=>'1234567890','name'=>'John Doe', 'admin'=>true];
$payload_encoded = base64url_encode(json_encode($payload));

//build the signature
$key = 'secret';
$signature = hash_hmac('SHA256',"$headers_encoded.$payload_encoded",$key);

//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature";
echo $token;

base64url_encode 函数:

function base64url_encode($data) {
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

我的 JWT 头和负载与验证站点默认的 JWT 完全匹配,但是我的签名不匹配,因此我的令牌被标记为无效。这个标准似乎非常简单,那么我的签名有什么问题呢?

3个回答

40

我解决了!我没有意识到需要对签名本身进行base64编码。此外,我需要将hash_hmac函数的最后一个可选参数设置为$raw_output=true(请参见文档)。简而言之,我需要将我的代码从原来的更改为:

//build the signature
$key = 'secret';
$signature = hash_hmac('sha256',"$headers_encoded.$payload_encoded",$key);

//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature";

改正后:

//build the signature
$key = 'secret';
$signature = hash_hmac('sha256',"$headers_encoded.$payload_encoded",$key,true);
$signature_encoded = base64url_encode($signature);

//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature_encoded";
echo $token;

这在不同的网络和 URL 上能够运作吗? 在 HTTP 服务器上生成此令牌并在 HTTPS 服务器上验证,但每次都失败了。 - MSQ

11

如果您想使用RS256(而不是像 OP 一样使用 HS256)来解决它,可以像这样使用:

//build the headers
$headers = ['alg'=>'RS256','typ'=>'JWT'];
$headers_encoded = base64url_encode(json_encode($headers));

//build the payload
$payload = ['sub'=>'1234567890','name'=>'John Doe', 'admin'=>true];
$payload_encoded = base64url_encode(json_encode($payload));

//build the signature
$key = "-----BEGIN PRIVATE KEY----- ....";
openssl_sign("$headers_encoded.$payload_encoded", $signature, $key, 'sha256WithRSAEncryption'); 
$signature_encoded = base64url_encode($signature);

//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature_encoded";
echo $token;

花费了我比愿意承认的时间


使用“openssl_sign()”解决了我的问题。当我尝试使用hash_hmac签名时,无法正确解密密钥值。但现在通过使用base64url_encode和openssl_sign解决了这个问题。感谢@Christian。 - Ganesh

2

https://github.com/gradus0/appleAuth 查看方法 $appleAuthObj->get_jwt_token()

<?php
include_once "appleAuth.class.php";

// https://developer.apple.com/account/resources/identifiers/list/serviceId -- indificator value
$clientId = ""; // com.youdomen
// your developer account id -> https://developer.apple.com/account/#/membership/
$teamId = "";
// key value show in -> https://developer.apple.com/account/resources/authkeys/list
$key = ""; 
// your page url where this script
$redirect_uri = ""; // example: youdomen.com/appleAuth.class.php
// path your key file, download file this -> https://developer.apple.com/account/resources/authkeys/list
$keyPath =''; // example: ./AuthKey_key.p8 

try{    
    $appleAuthObj = new \appleAuth\sign($clientId,$teamId,$key,$redirect_uri,$keyPath); 
    
    if(isset($_REQUEST['code'])){
        $jwt_token = $appleAuthObj->get_jwt_token($_REQUEST['code']);
        $response = $appleAuthObj->get_response($_REQUEST['code'],$jwt_token);
        $result_token = $this->read_id_token($response['read_id_token']);

        var_dump($response);
        var_dump($result_token);
    }else{
        $state = bin2hex(random_bytes(5));
        echo "<a href='".$appleAuthObj->get_url($state)."'>sign</a>";
    }
                                                                                    
} catch (\Exception $e) {
    echo "error: ".$e->getMessage();
}

1
您的回答可以通过添加更多支持信息来改进。请[编辑]以添加进一步的细节,例如引用或文献资料,以便其他人可以确认您的答案是否正确。您可以在帮助中心中找到有关编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接