【问题标题】:How to verify firebase ID token with PHP(JWT)?如何使用 PHP(JWT)验证 Firebase ID 令牌?
【发布时间】:2017-06-25 04:29:33
【问题描述】:

我有一个共享托管计划,其中只有 PHP(没有 Java,没有 node.js)。我需要从我的 android 应用程序发送 firebase ID 令牌并通过 PHP-JWT 进行验证。

我正在关注教程:Verify Firebase ID tokens

上面写着:

“如果您的后端使用的语言没有官方 Firebase Admin SDK,您仍然可以验证 ID 令牌。首先,为您的语言找到第三方 JWT 库。然后,验证标头、有效负载,和 ID 令牌的签名。”

我找到了那个库:Firebase-PHP-JWT。在 gitHub 示例中;我看不懂

$key 部分:

`$key = "example_key";` 

$token 部分:

`$token = array(
    "iss" => "http://example.org",
    "aud" => "http://example.com",
    "iat" => 1356999524,
    "nbf" => 1357000000
);`

我的问题:

  1. $key 变量应该是什么?
  2. 为什么 &token 变量是一个数组?将从移动应用发送的令牌是一个字符串。
  3. 如果有人可以发布使用 PHP-JWT 验证 Firebase ID 的完整示例,我将不胜感激。

编辑:

好吧,我明白了。 GitHub 示例展示了如何生成 JWT 代码(编码)以及如何对其进行解码。在我的情况下,我只需要解码由 firebase 编码的 jwt。所以,我只需要使用这段代码:

$decoded = JWT::decode($jwt, $key, array('HS256'));

在此代码部分中,$jwt 是 Firebase ID 令牌。对于 $key 变量文档说:

最后,确保 ID 令牌由与令牌的 Kid 声明对应的私钥签名。从https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com 获取公钥并使用 JWT 库来验证签名。使用来自该端点的响应的 Cache-Control 标头中的 max-age 的值来了解何时刷新公钥。

我不明白如何将这个公钥传递给解码函数。键是这样的:

“----- BEGIN CERTIFICATE ----- \ nMIIDHDCCAgSgAwIBAgIIZ36AHgMyvnQwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE \ nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTcw \ nMjA4MDA0NTI2WhcNMTcwMjExMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl \ nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD \ nggEPADCCAQoCggEBANBNTpiQplOYizNeLbs + r941T392wiuMWr1gSJEVykFyj7fe \ nCCIhS / zrmG9jxVMK905KwceO / FNB4SK + l8GYLb559xZeJ6MFJ7QmRfL7Fjkq7GHS \ N0 / sOFpjX7vfKjxH5oT65Fb1 + Hb4RzdoAjx0zRHkDIHIMiRzV0nYleplqLJXOAc6E \ n5HQros8iLdf + ASdqaN0hS0nU5aa / CPU / EHQwfbEgYraZLyn5NtH8SPKIwZIeM7Fr \ NNH + SS7JSadsqifrUBRtb // fueZ / FYlWqHEppsuIkbtaQmTjRycg35qpVSEACHkKc \ nW05rRsSvz7q1Hucw6Kx / dNBBbkyHrR4Mc / wg31kCAwEAAaM4MDYwDAYDVR0TAQH / \ nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH / BAwwCgYIKwYBBQUHAwIwDQYJ \ nKoZIhvcNAQEFBQADggEBAEuYEtvmZ4uReMQhE3P0iI4wkB36kWBe1mZZAwLA5A + U \ niEODMVKaaCGqZXrJTRhvEa20KRFrfuGQO7U3FgOMyWmX3drl40cNZNb3Ry8rsuVi \ nR1dxy6HpC39zba / DsgL07enZPMDksLRNv0dVZ / X / wMrTLrwwrglpCBYUlxGT9RrU \ nf8nAwLr1E4EpXxOVDXAX8bNBl3TCb2fu6DT62ZSmlJV 40K+wTRUlCqIewzJ0wMt6\nO8+6kVdgZH4iKLi8gVjdcFfNsEpboOBoZqjipJ63l4A3mfxOkma0d2XgKR12KAfYX\ncAVPgihAPoNoUPJK0Nj+CmvNlUBXCrl9TtqGjK7AKi8=\n-----结束证书-\

在传递之前我需要将这个公钥转换成什么东西吗?我试图删除所有 "\n""-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE -----"...但是没有运气。我仍然收到无效签名错误。有什么建议吗?

【问题讨论】:

  • Firebase 版本信息?请注意,您不会在 PHP 中验证令牌。您在那里铸造它们,将它们发送给客户,然后客户进行验证。
  • @Kato 我使用的是最新版本。 'com.google.firebase:firebase-auth:10.0.1'。我没明白你的意思。客户端在移动设备上登录后,firebase auth 会返回一个令牌。我想用 PHP 在服务器端验证这个令牌,以确保该令牌是否由 firebase 生成。如果验证没问题,我会授权客户。
  • @eren130,您知道公共验证密钥多久更改一次吗?我们应该将它们缓存一小时、一天还是一周?谢谢。
  • @andreszs "使用来自该端点的响应的 Cache-Control 标头中的 max-age 值来了解何时刷新公钥。"

标签: php android firebase firebase-authentication jwt


【解决方案1】:

如果还有人感兴趣,@CFP Support 的回答对于使用PHP 5.6 的服务器来说是相当不错的,但是它在尝试缓存当前保存的公钥的到期时间时确实存在一些错误。我已经使用了该代码,并进行了必要的更正:

要求 composer.json

{
    "require" : {
        "firebase/php-jwt": "5.2.0"
    }
}

用法

<?
$verified = verify_firebase_token(<THE TOKEN FROM FIREBASE>);
?>

函数

<?
# the file for the downloaded public keys
$jwt['keys'] = 'jwt.publickeys.json';

# this file contains the next time the system has to revalidate the keys
$jwt['cache'] = 'jwt.publickeys.cache';

# project ID
$jwt['project_id'] = YOUR_FIREBASE_PROJECT_ID;

# verify token
function verify_firebase_token($token) {
    global $jwt;
    $return = array();
    jwt_check_keys();
    $keys_raw = jwt_get_keys();
    if(!empty($keys_raw)) {
        $keys = json_decode($keys_raw, true);
        try {
            $decoded = \Firebase\JWT\JWT::decode($token, $keys, ['RS256']);
            if(!empty($decoded)) {
                # follow best practices verification-wise
                # https://firebase.google.com/docs/auth/admin/verify-id-tokens

                # exp must be in the future
                $exp = $decoded->exp > time();
                # ist must be in the past
                $iat = $decoded->iat < time();
                # aud must be firebase project ID
                $aud = $decoded->aud == $jwt['project_id'];
                # iss must be https://securetoken.google.com/<projectID>
                $iss = $decoded->iss == 'https://securetoken.google.com/'.$jwt['project_id'];
                # sub must be non-empty and is the UID of the user or device
                $sub = $decoded->sub;
                # check all items
                if($exp && $iat && $aud && $iss && !empty($sub)) {
                    # confirmed firebase user
                    $return['user']['uid'] = $sub;
                    // $return['user']['email'] = $decoded->email;
                    // $return['user']['name'] = $decoded->name;
                    // $return['user']['picture'] = $decoded->picture;
                    // $return['all'] = $decoded;
                } else {
                }
            }
        } catch (\UnexpectedValueException $unexpectedValueException) {
            $return['error'] = $unexpectedValueException->getMessage();
            //$unexpectedValueException->getMessage()
        }
    }
    return $return;
}

# checks whether new keys should be downloaded
# retrieves them if needed
function jwt_check_keys() {
    global $jwt;
    if(file_exists($jwt['cache'])) {
        $fp_cache = fopen($jwt['cache'], 'r+');
        if(flock($fp_cache, LOCK_SH)) {
            $cachetime = fread($fp_cache, filesize($jwt['cache']));
            if($cachetime > time()) {
                # still valid - do nothing
                flock($fp_cache, LOCK_UN);
            } elseif(flock($fp_cache, LOCK_EX)) {
                # expired - refresh public keys
                jwt_refresh_keys();
                flock($fp_cache, LOCK_UN);
            } else {
                throw new \RuntimeException('Cannot refresh keys: file lock upgrade error.');
            }
        } else {
            throw new \RuntimeException('Cannot refresh keys: file lock error.');
        }
        fclose($fp_cache);
    } else {
        # refresh public keys
        jwt_refresh_keys();
    }
}

# downloads the public keys and writes them in a file
# sets the new cache revalidation time
function jwt_refresh_keys() {
    global $jwt;
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HEADER, 1);
    $data = curl_exec($ch);
    $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    $headers = trim(substr($data, 0, $header_size));
    $raw_keys = trim(substr($data, $header_size));
    if(preg_match('/max-age=(\d+)/', $headers, $age_matches) === 1) {
        # update new cache expiration timestamp
        $fp_cache = fopen($jwt['cache'], 'w');
        $age = $age_matches[1];
        fwrite($fp_cache, ''.(time() + $age));
        fflush($fp_cache);

        # update public keys
        $fp_keys = fopen($jwt['keys'], 'w');
        if(flock($fp_keys, LOCK_EX)) {
            fwrite($fp_keys, $raw_keys);
            fflush($fp_keys);
            flock($fp_keys, LOCK_UN);
        }
        fclose($fp_keys);
    }
}

# retrieves the downloaded keys
# this should be called anytime you need the keys (i.e. for decoding / verification)
function jwt_get_keys() {
    global $jwt;
    $fp = fopen($jwt['keys'], 'r');
    $keys = null;
    if(flock($fp, LOCK_SH)) {
        $keys = fread($fp, filesize($jwt['keys']));
        flock($fp, LOCK_UN);
    }
    fclose($fp);
    return $keys;
}
?>

【讨论】:

【解决方案2】:

您可以查看以下库,而不是手动完成所有操作:
Firebase Tokens 甚至Firebase Admin SDK for PHP。 缓存等东西已经实现了,看看文档就行了。

基本上,您只需使用 Firebase 令牌库执行以下操作:

use Firebase\Auth\Token\HttpKeyStore;
use Firebase\Auth\Token\Verifier;
use Symfony\Component\Cache\Simple\FilesystemCache;

$cache = new FilesystemCache();
$keyStore = new HttpKeyStore(null, $cache);
$verifier = new Verifier($projectId, $keyStore);

    try {
        $verifiedIdToken = $verifier->verifyIdToken($idToken);

        // "If all the above verifications are successful, you can use the subject 
        // (sub) of the ID token as the uid of the corresponding user or device. (see https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library)
        echo $verifiedIdToken->getClaim('sub'); // "a-uid"
    } catch (\Firebase\Auth\Token\Exception\ExpiredToken $e) {
        echo $e->getMessage();
    } catch (\Firebase\Auth\Token\Exception\IssuedInTheFuture $e) {
        echo $e->getMessage();
    } catch (\Firebase\Auth\Token\Exception\InvalidToken $e) {
        echo $e->getMessage();
    }

【讨论】:

  • 请注意,这需要 PHP 7+。只是提醒那些可能出于任何原因使用 5.x 的人。
  • 什么是项目ID?
  • @llaaalu projectId 应该是您的 Firebase 项目 ID。
【解决方案3】:

接受答案的工作示例。 注意差异:

  • 经过测试和工作

  • 在非类环境中工作

  • 更多代码展示了如何将其用于 Firebase(简单、单行发送代码进行验证)

  • UnexpectedValueException 涵盖了您可能会看到的各种错误(例如过期/无效的键)

  • 评论很好,易于理解

  • 从 Firebase 令牌返回一组 VERIFIED 数据(您可以安全地将这些数据用于您需要的任何事情)

这基本上是 https://firebase.google.com/docs/auth/admin/verify-id-tokens 的分解、易于阅读/理解的 PHP 版本

注意:您可以使用 getKeys()、refreshKeys()、checkKeys() 函数来生成用于任何安全 api 情况的密钥(用您自己的模仿 'verify_firebase_token' 函数的功能)。

使用:

$verified_array = verify_firebase_token(<THE TOKEN FROM FIREBASE>)

代码:

$keys_file = "securetoken.json"; // the file for the downloaded public keys
$cache_file = "pkeys.cache"; // this file contains the next time the system has to revalidate the keys
//////////  MUST REPLACE <YOUR FIREBASE PROJECTID> with your own!
$fbProjectId = <YOUR FIREBASE PROJECTID>;

/////// FROM THIS POINT, YOU CAN COPY/PASTE - NO CHANGES REQUIRED
///  (though read through for various comments!)
function verify_firebase_token($token = '')
{
    global $fbProjectId;
    $return = array();
    $userId = $deviceId = "";
    checkKeys();
    $pkeys_raw = getKeys();
    if (!empty($pkeys_raw)) {
        $pkeys = json_decode($pkeys_raw, true);
        try {
            $decoded = \Firebase\JWT\JWT::decode($token, $pkeys, ["RS256"]);
            if (!empty($_GET['debug'])) {
                echo "<hr>BOTTOM LINE - the decoded data<br>";
                print_r($decoded);
                echo "<hr>";
            }
            if (!empty($decoded)) {
                // do all the verifications Firebase says to do as per https://firebase.google.com/docs/auth/admin/verify-id-tokens
                // exp must be in the future
                $exp = $decoded->exp > time();
                // ist must be in the past
                $iat = $decoded->iat < time();
                // aud must be your Firebase project ID
                $aud = $decoded->aud == $fbProjectId;
                // iss must be "https://securetoken.google.com/<projectId>"
                $iss = $decoded->iss == "https://securetoken.google.com/$fbProjectId";
                // sub must be non-empty and is the UID of the user or device
                $sub = $decoded->sub;
                if ($exp && $iat && $aud && $iss && !empty($sub)) {
                    // we have a confirmed Firebase user!
                    // build an array with data we need for further processing
                    $return['UID'] = $sub;
                    $return['email'] = $decoded->email;
                    $return['email_verified'] = $decoded->email_verified;
                    $return['name'] = $decoded->name;
                    $return['picture'] = $decoded->photo;
                } else {
                    if (!empty($_GET['debug'])) {
                        echo "NOT ALL THE THINGS WERE TRUE!<br>";
                        echo "exp is $exp<br>ist is $iat<br>aud is $aud<br>iss is $iss<br>sub is $sub<br>";
                    }
                    /////// DO FURTHER PROCESSING IF YOU NEED TO
                    // (if $sub is false you may want to still return the data or even enter the verified user into the database at this point.)
                }
            }
        } catch (\UnexpectedValueException $unexpectedValueException) {
            $return['error'] = $unexpectedValueException->getMessage();
            if (!empty($_GET['debug'])) {
                echo "<hr>ERROR! " . $unexpectedValueException->getMessage() . "<hr>";
            }
        }
    }
    return $return;
}
/**
* Checks whether new keys should be downloaded, and retrieves them, if needed.
*/
function checkKeys()
{
    global $cache_file;
    if (file_exists($cache_file)) {
        $fp = fopen($cache_file, "r+");
        if (flock($fp, LOCK_SH)) {
            $contents = fread($fp, filesize($cache_file));
            if ($contents > time()) {
                flock($fp, LOCK_UN);
            } elseif (flock($fp, LOCK_EX)) { // upgrading the lock to exclusive (write)
                // here we need to revalidate since another process could've got to the LOCK_EX part before this
                if (fread($fp, filesize($cache_file)) <= time()) 
                {
                    refreshKeys($fp);
                }
                flock($fp, LOCK_UN);
            } else {
                throw new \RuntimeException('Cannot refresh keys: file lock upgrade error.');
            }
        } else {
            // you need to handle this by signaling error
        throw new \RuntimeException('Cannot refresh keys: file lock error.');
        }
        fclose($fp);
    } else {
        refreshKeys();
    }
}

/**
 * Downloads the public keys and writes them in a file. This also sets the new cache revalidation time.
 * @param null $fp the file pointer of the cache time file
 */
function refreshKeys($fp = null)
{
    global $keys_file;
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HEADER, 1);
    $data = curl_exec($ch);
    $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    $headers = trim(substr($data, 0, $header_size));
    $raw_keys = trim(substr($data, $header_size));
    if (preg_match('/age:[ ]+?(\d+)/i', $headers, $age_matches) === 1) 
    {
        $age = $age_matches[1];
        if (preg_match('/cache-control:.+?max-age=(\d+)/i', $headers, $max_age_matches) === 1) {
            $valid_for = $max_age_matches[1] - $age;
            $fp = fopen($keys_file, "w");
            ftruncate($fp, 0);
            fwrite($fp, "" . (time() + $valid_for));
            fflush($fp);
            // $fp will be closed outside, we don't have to
            $fp_keys = fopen($keys_file, "w");
            if (flock($fp_keys, LOCK_EX)) {
                fwrite($fp_keys, $raw_keys);
                fflush($fp_keys);
                flock($fp_keys, LOCK_UN);
            }
            fclose($fp_keys);
        }
    }
}

/**
 * Retrieves the downloaded keys.
 * This should be called anytime you need the keys (i.e. for decoding / verification).
 * @return null|string
 */
function getKeys()
{
   global $keys_file;
    $fp = fopen($keys_file, "r");
    $keys = null;
    if (flock($fp, LOCK_SH)) {
        $keys = fread($fp, filesize($keys_file));
        flock($fp, LOCK_UN);
    }
    fclose($fp);
    return $keys;
}

【讨论】:

  • 嗨,你能解释一下你的function get_userId_by_deviceToken($subToken)里面的$subToken在这里是做什么用的吗?那是 idtoken 吗?
  • $subToken 指的是 $decoded->sub (参见 $userId = get_userId_by_deviceToken($decoded->sub); 行)如果您从 Firebase 存储订阅者令牌,您可以使用该函数从您的数据库中提取 userId。您不必使用 $decoded->sub,您可以调用您的数据库从 $decoded->email 或其他任何内容中获取 userId。
  • 啊,我明白了。我错过了那部分;我认为 userId 与其余信息一起在一个数组中返回,我将使用它来将信息添加到我的数据库中。我还需要get_userId_by_deviceToken 函数吗?我正计划使用 HTTP POST 请求发送我的 idtoken,就像这里使用的那样:https://developers.google.com/identity/sign-in/web/backend-auth
  • 我更改了代码 - 抱歉那部分让您感到困惑....... //(如果 $sub 为假,此时您可能仍希望返回数据,甚至将经过验证的用户输入数据库。)}”,因此,您不会“之前从我的互联网服务器获取它... "
  • 另外,您的refreshKeys 方法是在securetoken.json 而不是pkeys.cache 中写入密钥重新验证时间!因此,您永远不会使用缓存。该示例保持每次重新下载密钥。要修复它,请在 ftruncate($fp, 0); 之前使用 $fp = fopen($cache_file, "w"); 并在该函数上声明 global $cache_file;。最后:ftruncate 使用模式 w 时不需要 ftruncate,它会自动截断文件。
【解决方案4】:

仅当您使用密码对令牌进行签名时才使用 HS256。 Firebase 在发布令牌时使用 RS256,因此,您需要来自给定 URL 的公钥,并且您需要将算法设置为 RS256。

还请注意,您在应用程序中获得的令牌不应是一个数组,而是一个包含 3 个部分的字符串:headerbodysignature。每个部分由. 分隔,因此它为您提供了一个简单的字符串:header.body.signature

为了验证令牌,您需要做的是定期从given URL 下载公钥(检查Cache-Control 标头以获取该信息)并将其(JSON)保存在一个文件中,这样您就赢了不必每次需要检查 JWT 时都检索它。然后你可以读入文件并解码 JSON。解码后的对象可以传递给JWT::decode(...) 函数。 这是一个简短的示例:

$pkeys_raw = file_get_contents("cached_public_keys.json");
$pkeys = json_decode($pkeys_raw, true);

$decoded = JWT::decode($token, $pkeys, ["RS256"]);

现在$decoded 变量包含令牌的有效负载。一旦你有解码的对象,你仍然需要验证它。根据the guide关于ID令牌验证,你必须检查以下几点:

  • exp是未来
  • iat 过去了
  • iss: https://securetoken.google.com/&lt;firebaseProjectID&gt;
  • aud: &lt;firebaseProjectID&gt;
  • sub 非空

因此,例如,您可以像这样检查iss(其中FIREBASE_APP_ID 是来自firebase 控制台的应用ID):

$iss_is_valid = isset($decoded->iss) && $decoded->iss === "https://securetoken.google.com/" . FIREBASE_APP_ID;

这里是刷新和检索密钥的完整示例。

免责声明:我尚未对其进行测试,这基本上仅供参考。

$keys_file = "securetoken.json"; // the file for the downloaded public keys
$cache_file = "pkeys.cache"; // this file contains the next time the system has to revalidate the keys

/**
 * Checks whether new keys should be downloaded, and retrieves them, if needed.
 */
function checkKeys()
{
    if (file_exists($cache_file)) {
        $fp = fopen($cache_file, "r+");

        if (flock($fp, LOCK_SH)) {
            $contents = fread($fp, filesize($cache_file));
            if ($contents > time()) {
                flock($fp, LOCK_UN);
            } elseif (flock($fp, LOCK_EX)) { // upgrading the lock to exclusive (write)
                // here we need to revalidate since another process could've got to the LOCK_EX part before this
                if (fread($fp, filesize($this->cache_file)) <= time()) {
                    $this->refreshKeys($fp);
                }
                flock($fp, LOCK_UN);
            } else {
                throw new \RuntimeException('Cannot refresh keys: file lock upgrade error.');
            }
        } else {
            // you need to handle this by signaling error
            throw new \RuntimeException('Cannot refresh keys: file lock error.');
        }

        fclose($fp);
    } else {
        refreshKeys();
    }
}

/**
 * Downloads the public keys and writes them in a file. This also sets the new cache revalidation time.
 * @param null $fp the file pointer of the cache time file
 */
function refreshKeys($fp = null)
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HEADER, 1);

    $data = curl_exec($ch);

    $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    $headers = trim(substr($data, 0, $header_size));
    $raw_keys = trim(substr($data, $header_size));

    if (preg_match('/age:[ ]+?(\d+)/i', $headers, $age_matches) === 1) {
        $age = $age_matches[1];

        if (preg_match('/cache-control:.+?max-age=(\d+)/i', $headers, $max_age_matches) === 1) {
            $valid_for = $max_age_matches[1] - $age;
            ftruncate($fp, 0);
            fwrite($fp, "" . (time() + $valid_for));
            fflush($fp);
            // $fp will be closed outside, we don't have to

            $fp_keys = fopen($keys_file, "w");
            if (flock($fp_keys, LOCK_EX)) {
                fwrite($fp_keys, $raw_keys);
                fflush($fp_keys);
                flock($fp_keys, LOCK_UN);
            }
            fclose($fp_keys);
        }
    }
}

/**
 * Retrieves the downloaded keys.
 * This should be called anytime you need the keys (i.e. for decoding / verification).
 * @return null|string
 */
function getKeys()
{
    $fp = fopen($keys_file, "r");
    $keys = null;

    if (flock($fp, LOCK_SH)) {
        $keys = fread($fp, filesize($keys_file));
        flock($fp, LOCK_UN);
    }

    fclose($fp);

    return $keys;
}

最好的办法是安排一个 cronjob 在需要时调用checkKeys(),但我不知道您的提供商是否允许这样做。取而代之的是,您可以为每个请求执行此操作:

checkKeys();
$pkeys_raw = getKeys(); // check if $raw_keys is not null before using it!

【讨论】:

  • 谢谢!救了我的命……最后一件事。您能否编辑您的答案并添加根据“Cache-Control:max-age”缓存公钥的代码示例并在 max-age 时间到期时更新它们?这将是验证 Firebase ID 的完整示例源。对不起,我可怜的 PHP 知识......顺便说一下,我已经有两天在玩 firebase auth 了。来自给定 url 的“Cache-Control”标头始终为“max-age=0”。那么,为什么需要缓存呢?
  • 我刚刚在 Firefox 和 Chrome 中打开了它,它们显示了一个有效的 max-age 值。你如何检索标题?
  • 我在 Firefox 上使用“Live HTTP Headers”扩展。抱歉,刚才我看到了,有两个单独的标题:prntscr.com/e6ht8d
  • @eren130 我的意思是你如何在 PHP 中检索 URL?你用 cURL 吗?
  • @Josh 我不知道您是否了解非对称加密或使用密钥签名/验证事物,但简而言之,需要 private 密钥来创建这样的令牌,可以使用您在此示例中下载和使用的 public 密钥进行验证。私钥不公开,不能以任何方式访问,并且是秘密的,因此是 private。因此,回答您的问题:是的,如果您确认此令牌来自 Firebase,您可以安全地使用数据库中的 UID,而无需进一步检查 Firebase 数据库。
猜你喜欢
  • 2021-04-02
  • 2018-07-23
  • 2018-06-12
  • 2019-04-09
  • 2017-01-24
  • 2019-06-28
  • 2017-07-31
  • 2018-10-16
  • 1970-01-01
相关资源
最近更新 更多