如何使用 PHP(JWT)验证 Firebase ID 令牌?
我有一个只有 PHP 的共享主机计划(没有 Java,没有 node.js).我需要从我的 android 应用程序发送 firebase ID 令牌并通过 PHP-JWT 进行验证.
I have a shared hosting plan which has only PHP(no Java, no node.js). I need to send firebase ID token from my android app and verify it by PHP-JWT.
我正在学习教程:验证 Firebase ID 令牌
它说:
如果您的后端使用没有官方 Firebase Admin SDK 的语言,您仍然可以验证 ID 令牌.首先,为您的语言找到第三方 JWT 库.然后,验证标头、有效负载、和 ID 令牌的签名."
"If your backend is in a language that doesn't have an official Firebase Admin SDK, you can still verify ID tokens. First, find a third-party JWT library for your language. Then, verify the header, payload, and signature of the ID token."
我找到了该库:Firebase-PHP-JWT.在 gitHub 示例中;我无法理解
I found that library: Firebase-PHP-JWT. In gitHub example; i couldn't understand the
$key 部分:
`$key = "example_key";`
和
$token 部分:
`$token = array(
"iss" => "http://example.org",
"aud" => "http://example.com",
"iat" => 1356999524,
"nbf" => 1357000000
);`
我的问题:
- $key 变量应该是什么?
- 为什么 &token 变量是一个数组?将从移动应用发送的令牌是一个字符串.
- 如果有人可以发布使用 PHP-JWT 验证 Firebase ID 的完整示例,我将不胜感激.
- What should be the $key variable?
- Why the &token variable is an array? Token which will be sent from mobile app is a String.
- If somebody could post a full example of verifying firebase ID with PHP-JWT, i would appreciate it.
好的,我明白了.GitHub 示例展示了如何生成 JWT 代码(编码)以及如何对其进行解码.就我而言,我只需要解码由 firebase 编码的 jwt.所以,我只需要使用这个代码:
Okey i got the point. GitHub example shows how to generate JWT code(encode) and how to decode it. In my case i need only decode the jwt which encoded by firebase. So, i need to use only this code:
$decoded = JWT::decode($jwt, $key, array('HS256'));
在此代码部分中,$jwt 是 Firebase ID 令牌.对于 $key 变量文档说:
In this code part $jwt is the firebase ID token. For $key variable documentation says:
最后,确保 ID 令牌由与令牌的孩子声明对应的私钥签名.从 https://www.googleapis 获取公钥.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com 并使用 JWT 库来验证签名.使用来自该端点的响应的 Cache-Control 标头中的 max-age 值来了解何时刷新公钥.
Finally, ensure that the ID token was signed by the private key corresponding to the token's kid claim. Grab the public key from https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com and use a JWT library to verify the signature. Use the value of max-age in the Cache-Control header of the response from that endpoint to know when to refresh the public keys.
我不明白如何通过这个公钥来解码函数.键是这样的:
----- 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 nf8nAwLr1E4EpXxOVDXAX8bNBl3TCb2fu6DT62ZSmlJV40K+wTRUlCqIewzJ0wMt6 O8+6kVdgZH4iKLi8gVjdcFfNsEpbOBoZqjipJ63l4A3mfxOkma0d2XgKR12KAfYX cAVPgihAPoNoUPJK0Nj+CUBjKtlvC----C-UBGrjKnp--CRTImKnp--C-FfNsEpboZqjipJ63l4A3mfxOkma0d2XgKR12
"-----BEGIN CERTIFICATE----- MIIDHDCCAgSgAwIBAgIIZ36AHgMyvnQwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE AxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTcw MjA4MDA0NTI2WhcNMTcwMjExMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl bi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBANBNTpiQplOYizNeLbs+r941T392wiuMWr1gSJEVykFyj7fe CCIhS/zrmG9jxVMK905KwceO/FNB4SK+l8GYLb559xZeJ6MFJ7QmRfL7Fjkq7GHS 0/sOFpjX7vfKjxH5oT65Fb1+Hb4RzdoAjx0zRHkDIHIMiRzV0nYleplqLJXOAc6E 5HQros8iLdf+ASdqaN0hS0nU5aa/cPu/EHQwfbEgYraZLyn5NtH8SPKIwZIeM7Fr nh+SS7JSadsqifrUBRtb//fueZ/FYlWqHEppsuIkbtaQmTjRycg35qpVSEACHkKc W05rRsSvz7q1Hucw6Kx/dNBBbkyHrR4Mc/wg31kCAwEAAaM4MDYwDAYDVR0TAQH/ BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ KoZIhvcNAQEFBQADggEBAEuYEtvmZ4uReMQhE3P0iI4wkB36kWBe1mZZAwLA5A+U iEODMVKaaCGqZXrJTRhvEa20KRFrfuGQO7U3FgOMyWmX3drl40cNZNb3Ry8rsuVi R1dxy6HpC39zba/DsgL07enZPMDksLRNv0dVZ/X/wMrTLrwwrglpCBYUlxGT9RrU f8nAwLr1E4EpXxOVDXAX8bNBl3TCb2fu6DT62ZSmlJV40K+wTRUlCqIewzJ0wMt6 O8+6kVdgZH4iKLi8gVjdcFfNsEpbOBoZqjipJ63l4A3mfxOkma0d2XgKR12KAfYX cAVPgihAPoNoUPJK0Nj+CmvNlUBXCrl9TtqGjK7AKi8= -----END CERTIFICATE----- "
在传递之前我需要将这个公钥转换成什么东西吗?我试图删除所有 " " 和 "-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----"...但没有运气.我仍然收到无效签名错误.有什么建议吗?
Do i need to convert this public key to something before pass it? I tried to remove all " " and "-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----"...But no luck. Still i get invalid signature error. Any advice?
推荐答案
HS256 仅在您使用密码对令牌进行签名时使用.Firebase 在发出令牌时使用 RS256,因此,您需要来自给定 URL 的公钥,并且需要将算法设置为 RS256.
HS256 is used only if you use a password to sign the token. Firebase uses RS256 when it issues a token, thus, you need the public keys from the given URL, and you need to set the algorithm to RS256.
另请注意,您在应用程序中获得的令牌不应是一个数组,而是一个包含 3 个部分的字符串:header
、body
和 signature代码>.每个部分都由
.
分隔,因此它为您提供了一个简单的字符串:header.body.signature
Also note that the token you get in your application should not be an array but a string that has 3 parts: header
, body
, and signature
. Each part is separated by a .
, thus it gives you a simple string: header.body.signature
为了验证令牌,您需要做的是从 给定 URL(检查 Cache-Control
标头以获取该信息)并将其(JSON)保存在文件中,这样您就不会每次需要检查 JWT 时都可以检索它.然后您可以读入文件并解码 JSON.解码后的对象可以传递给 JWT::decode(...)
函数.这是一个简短的示例:
What you need to do in order to verify the tokens is downloading the public keys from the given URL regularly (check the Cache-Control
header for that info) and saving it (the JSON) in a file, so you won't have to retrieve it every time you need to check the JWT. Then you can read in the file and decode the JSON. The decoded object can be passed to the JWT::decode(...)
function.
Here's a short sample:
$pkeys_raw = file_get_contents("cached_public_keys.json");
$pkeys = json_decode($pkeys_raw, true);
$decoded = JWT::decode($token, $pkeys, ["RS256"]);
现在 $decoded
变量包含令牌的有效载荷.获得解码后的对象后,您仍然需要对其进行验证.根据关于 ID 令牌验证的指南,您必须检查以下事项:
Now the $decoded
variable contains the payload of the token. Once you have the decoded object, you still need to verify it. According to the guide on ID token verification, you have to check the following things:
exp
在未来iat
已经过去了iss
:https://securetoken.google.com/
aud
:sub
非空
exp
is in the futureiat
is in the pastiss
:https://securetoken.google.com/<firebaseProjectID>
aud
:<firebaseProjectID>
sub
is non-empty
因此,例如,您可以像这样检查 iss
(其中 FIREBASE_APP_ID
是来自 firebase 控制台的应用 ID):
So, for example, you can check iss
like this (where FIREBASE_APP_ID
is the app ID from the firebase console):
$iss_is_valid = isset($decoded->iss) && $decoded->iss === "https://securetoken.google.com/" . FIREBASE_APP_ID;
<小时>
这是一个用于刷新和检索密钥的完整示例.
Here is a complete sample for refreshing the keys and retrieving them.
免责声明:我还没有测试过,这基本上仅供参考.
$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()
,但我不知道您的提供商是否允许这样做.取而代之的是,您可以为每个请求执行此操作:
The best thing would be scheduling a cronjob to call checkKeys()
whenever needed, but I don't know if your provider allows that. Instead of that, you can do this for every request:
checkKeys();
$pkeys_raw = getKeys(); // check if $raw_keys is not null before using it!
相关文章