JWT PHP 示例代码

PHP 支持 JWT 机制。

JWT 将用户信息加密到 token 里,客户端与服务端通过 JWT 交互,服务器不保存任何用户信息。

服务器通过使用保存的密钥验证 token 的正确性,只要正确即通过验证。

基于token的身份验证可以替代传统的cookie+session身份验证方法。

以下示例以 JWT 官网为例:

header 部分:

{
    "alg": "HS256",
    "typ": "JWT"
}

对应 base64UrlEncode 编码为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9。

header 部分说明:该字段为 json 格式,其中 alg 字段指定了生成signature的算法,默认值为 HS256,typ默认值为 JWT。

payload 部分:

{
    "sub": "1234567890",
    "name": "John Doe",
    "iat": 1516239022
}   

对应 base64UrlEncode 编码为:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ。

payload 部分说明:该字段为 json 格式,表明用户身份的数据,可以自己自定义字段,很灵活。sub 面向的用户,name 姓名,iat 签发时间。

PHP 使用 JWT 的示例代码:

<?php
/**
* PHP实现jwt
*/
class Jwt {
    //头部
    private static $header = array(
        'alg'=>'HS256', //生成signature的算法
        'typ'=>'JWT'    //类型
    );

    //使用HMAC生成信息摘要时所使用的密钥
    private static $key='aizwsXHXX6usCJdJ';

    /**
    * 获取 jwt token
    * @param array $payload jwt载荷  格式如下非必须
    * [
    *  'iss'=>'jwt_admin',  //该JWT的签发者
    *  'iat'=>time(),  //签发时间
    *  'exp'=>time()+7200,  //过期时间
    *  'nbf'=>time()+60,  //该时间之前不接收处理该Token
    *  'sub'=>'www.aizws.net',  //面向的用户
    *  'jti'=>md5(uniqid('JWT').time())  //该Token唯一标识
    * ]
    * @return bool|string
    */
    public static function getToken(array $payload)
    {
        if(is_array($payload))
        {
            $base64header=self::base64UrlEncode(json_encode(self::$header,JSON_UNESCAPED_UNICODE));
            $base64payload=self::base64UrlEncode(json_encode($payload,JSON_UNESCAPED_UNICODE));
            $token=$base64header.'.'.$base64payload.'.'.self::signature($base64header.'.'.$base64payload,self::$key,self::$header['alg']);
            return $token;
        }else{
            return false;
        }
    }

    /**
    * 验证token是否有效,默认验证exp,nbf,iat时间
    * @param string $Token 需要验证的token
    * @return bool|string
    */
    public static function verifyToken(string $Token)
    {
        $tokens = explode('.', $Token);
        if (count($tokens) != 3)
            return false;

        list($base64header, $base64payload, $sign) = $tokens;

        //获取jwt算法
        $base64decodeheader = json_decode(self::base64UrlDecode($base64header), JSON_OBJECT_AS_ARRAY);
        if (empty($base64decodeheader['alg']))
            return false;

        //签名验证
        if (self::signature($base64header . '.' . $base64payload, self::$key, $base64decodeheader['alg']) !== $sign)
            return false;

        $payload = json_decode(self::base64UrlDecode($base64payload), JSON_OBJECT_AS_ARRAY);

        //签发时间大于当前服务器时间验证失败
        if (isset($payload['iat']) && $payload['iat'] > time())
            return false;

        //过期时间小宇当前服务器时间验证失败
        if (isset($payload['exp']) && $payload['exp'] < time())
            return false;

        //该nbf时间之前不接收处理该Token
        if (isset($payload['nbf']) && $payload['nbf'] > time())
            return false;

        return $payload;
    }

    /**
    * base64UrlEncode   https://jwt.io/  中base64UrlEncode编码实现
    * @param string $input 需要编码的字符串
    * @return string
    */
    private static function base64UrlEncode(string $input)
    {
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
    }

    /**
    * base64UrlEncode  https://jwt.io/  中base64UrlEncode解码实现
    * @param string $input 需要解码的字符串
    * @return bool|string
    */
    private static function base64UrlDecode(string $input)
    {
        $remainder = strlen($input) % 4;
        if ($remainder) {
            $addlen = 4 - $remainder;
            $input .= str_repeat('=', $addlen);
        }
        return base64_decode(strtr($input, '-_', '+/'));
    }

    /**
    * HMACSHA256签名   https://jwt.io/  中HMACSHA256签名实现
    * @param string $input 为base64UrlEncode(header).".".base64UrlEncode(payload)
    * @param string $key
    * @param string $alg   算法方式
    * @return mixed
    */
    private static function signature(string $input, string $key, string $alg = 'HS256')
    {
        $alg_config=array(
            'HS256'=>'sha256'
        );
        return self::base64UrlEncode(hash_hmac($alg_config[$alg], $input, $key,true));
    }
}

//测试数据是否匹配
//增加自定义数据字段,比如携带用户ID:'uid'=>1 
$payload=array('iss'=>'admin','iat'=>time(),'exp'=>time()+7200,'nbf'=>time(),'sub'=>'www.aizws.net','jti'=>md5(uniqid('JWT').time()),'uid'=>1);
$token=Jwt::getToken($payload);
echo $token;
echo "<br><br>";

//对token进行验证签名
$payload_verify=Jwt::verifyToken($token);
var_dump($payload_verify);

//可以依据 uid 进行验证。 

也可以使用第三方库,比如:https://github.com/firebase/php-jwt

下一章:HMAC SHA256 算法原理和实现

Golang 通过第三方库支持 JWT,JWT的签名算法使用了 HMAC SHA256 算法。JWT 通常在请求头中的Authorization字段,使用Bearer schema:Auth ...