今天遇到个坑,记录一下。
在记录之前,还是要补充装环境的坑。
前段时间记录了一下ThinkPhp5不知道多少,开发环境是macos11,因为macos自带apache,php开发环境,直接把apache的配置文件的http.conf里的php开关打开即可。但是换了设备,开发环境是macos12,系统自带的apache把php去掉了,所以配置php环境,除了安装php,还在apache的http.conf文件添加php运行路径,还要倒入相关模块,还要使用证书对http.con文件进行签名(我到这一步就放弃了,因为我没找到那个证书签名)。那只能换一种方式了,就是使用brew安装php,然后还要安装httpd,还要在其配置文件添加运行环境和导入模块(具体可以参考这个)。
还要配置环境
export PATH="/usr/local/opt/php@7.4/bin:$PATH"
export PATH="/opt/homebrew/bin:$PATH"
还要安装composer工具包安装工具。
brew install composer
安装好之后,可以创建一个php项目。
composer create-project topthink/think=版本号 项目名称
好的,那么再记录一下今天这个坑。
因为换电脑,我把代码上传到了gitee,然后把代码拉下来,然后就跑不起来了,奇奇怪怪。
主要还是那个Firebase\JWT\JWT 这个包,JWT的encode 和 decode 和之前的有区别。
先对比一下前后的代码
先前的代码
private static function generateToken($data){
$key = 'key'; //秘钥:自定义
$payload = array(
'iss' => 'my', //签发人(官方字段:非必需)
'aud' => 'public', //受众(官方字段:非必需)
'iat' => time(), //签发时间
'nbf' => time(), //生效时间,立即生效
'exp' => time() + 60*60*24*7, //过期时间,一周
'data' => $data, //自定义字段
);
//加密生成token
return JWT::encode($payload, $key);
}
public static function checkToken($request){
$authorization = $request->header("authorization");
// 获取token
{
// 异常捕获无效
try {
$token = substr($authorization,8,-1);
}catch (\Exception $ex){
$token = $authorization;
}
}
try {
// 1.如果当前时间大于 exp,或者小于nbf,token无效,进行拦截
$key = 'key';
JWT::$leeway = 60;//当前时间减去60,把时间留点余地
$decode = JWT::decode($token, $key, array('HS256'));
// 查数据库,用户不存在
if(Users::where('uid', $decode->data)->find()){
// 比较当前时间大于 exp,或者小于nbf,token无效,进行拦截
if($decode->nbf > time()){
return "权限伪造!";
}elseif ($decode->exp < time()){
return "权限过期,请重新登录!";
}
}else{
return "账户不存在!";
}
}catch (\Exception $ex){
// token 无效
return "权限不足!";
}
return true;
}
之后的代码
private static function generateToken($data): string
{
$key = 'key'; //秘钥:自定义
$payload = array(
'iss' => 'my', //签发人(官方字段:非必需)
'aud' => 'public', //受众(官方字段:非必需)
'iat' => time(), //签发时间
'nbf' => time(), //生效时间,立即生效
'exp' => time() + 60*60*24*7, //过期时间,一周
'data' => $data, //自定义字段
);
$keyId = "keyId";
//加密生成token
return JWT::encode($payload, $key, 'HS256', $keyId);
}
public static function checkToken($request){
$authorization = $request->header("authorization");
// 获取token
{
// 异常捕获无效
try {
$token = substr($authorization,7);
}catch (\Exception $ex){
$token = $authorization;
}
}
try {
// 1.如果当前时间大于 exp,或者小于nbf,token无效,进行拦截
JWT::$leeway = 60;//当前时间减去60,把时间留点余地
// ==========================主要是下面这一段================================
$key = new Key('key', 'HS256');
$decode = JWT::decode($token, $key);
// ==========================主要是上面面这一段================================
// 查数据库,用户不存在
if(Users::where('uid', $decode->data)->find()){
// 比较当前时间大于 exp,或者小于nbf,token无效,进行拦截
if($decode->nbf > time()){
return "权限伪造!";
}elseif ($decode->exp < time()){
return "权限过期,请重新登录!";
}
}else{
return "账户不存在!";
}
}catch (\Exception $ex){
// token 无效
echo $ex;
return "权限不足!";
}
return true;
}
把前后两段代码的关键部分揪出来对比一下:
# encode
$key = 'key'; //秘钥:自定义
$payload = array(...);
return JWT::encode($payload, $key);
# decode
$key = 'key';
JWT::$leeway = 60;//当前时间减去60,把时间留点余地
$decode = JWT::decode($token, $key, array('HS256'));
===================================================================
# encode
$key = 'key'; //秘钥:自定义
$payload = array(...);
$keyId = "keyId"; //这个东西必须要加上,不加上,报错,报错内容:'"kid" empty, unable to lookup correct key'
//加密生成token
return JWT::encode($payload, $key, 'HS256', $keyId);
# decode
JWT::$leeway = 60;//当前时间减去60,把时间留点余地
$key = new Key('key', 'HS256'); // 必须是 Firebase\JWT\Key;的对象
$decode = JWT::decode($token, $key);
分析一下源码
主要是这里的$keyId,这个参数给了个初始值null,把这个值$header[‘kid’] = $keyId;,但是在解码的时候,必须要有值,既然要求解码必须有值并且不为null,那个这里就不给出初始值,给开发者来传参就好了,这样友善一点。
public static function encode(
array $payload,
$key,
string $alg,
string $keyId = null,
array $head = null
): string {
$header = ['typ' => 'JWT', 'alg' => $alg];
if ($keyId !== null) {
$header['kid'] = $keyId; // 这里给$header['kid']赋值了,kid出错,原因在这
}
if (isset($head) && \is_array($head)) {
$header = \array_merge($head, $header);
}
$segments = [];
$segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header));
$segments[] = static::urlsafeB64Encode((string) static::jsonEncode($payload));
$signing_input = \implode('.', $segments);
$signature = static::sign($signing_input, $key, $alg);
$segments[] = static::urlsafeB64Encode($signature);
return \implode('.', $segments);
}
public static function decode(
string $jwt,
$keyOrKeyArray
): stdClass {
// Validate JWT
$timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp;
if (empty($keyOrKeyArray)) {
throw new InvalidArgumentException('Key may not be empty');
}
$tks = \explode('.', $jwt);
if (\count($tks) != 3) {
throw new UnexpectedValueException('Wrong number of segments');
}
// 对token进行了切分,分成了三部分
list($headb64, $bodyb64, $cryptob64) = $tks;
$headerRaw = static::urlsafeB64Decode($headb64);
if (null === ($header = static::jsonDecode($headerRaw))) {
throw new UnexpectedValueException('Invalid header encoding');
}
$payloadRaw = static::urlsafeB64Decode($bodyb64);
if (null === ($payload = static::jsonDecode($payloadRaw))) {
throw new UnexpectedValueException('Invalid claims encoding');
}
if (\is_array($payload)) {
// prevent PHP Fatal Error in edge-cases when payload is empty array
$payload = (object) $payload;
}
if (!$payload instanceof stdClass) {
throw new UnexpectedValueException('Payload must be a JSON object');
}
$sig = static::urlsafeB64Decode($cryptob64);
if (empty($header->alg)) {
throw new UnexpectedValueException('Empty algorithm');
}
if (empty(static::$supported_algs[$header->alg])) {
throw new UnexpectedValueException('Algorithm not supported');
}
// 主要是这里,要求$header->kid 不为null,值为null就报错。
$key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null);
// 这里对$key 要求是 Firebase\JWT\Key ,不然 没有getAlgorithm()()方法就拿不到Algorithm值
// Check the algorithm
if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) {
// See issue #351
throw new UnexpectedValueException('Incorrect key for this algorithm');
}
if ($header->alg === 'ES256' || $header->alg === 'ES384') {
// OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures
$sig = self::signatureToDER($sig);
}
// 这里对$key 要求是 Firebase\JWT\Key ,不然 没有getKeyMaterial()方法就拿不到Material值
if (!self::verify("$headb64.$bodyb64", $sig, $key->getKeyMaterial(), $header->alg)) {
throw new SignatureInvalidException('Signature verification failed');
}
// Check the nbf if it is defined. This is the time that the
// token can actually be used. If it's not yet that time, abort.
if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf)
);
}
// Check that this token has been created before 'now'. This prevents
// using tokens that have been created for later use (and haven't
// correctly used the nbf claim).
if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat)
);
}
// Check if this token has expired.
if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
throw new ExpiredException('Expired token');
}
return $payload;
}