2017-02-07 7 views
7

У меня есть общий план хостинга, который имеет только PHP (без Java, no node.js). Мне нужно отправить токен идентификатора firebase из моего приложения для Android и проверить его с помощью PHP-JWT.Как проверить токен базы данных firebase с помощью PHP (JWT)?

Я следую учебник: Verify Firebase ID tokens

Он говорит:.

«Если Бэкэнд на языке, который не имеет официального Firebase Admin SDK, вы можете проверить идентификационные маркеры Во-первых, найдите стороннюю библиотеку JWT для своего языка. Затем проверьте заголовок, полезную нагрузку и подпись маркера ID. "

Я нашел эту библиотеку: Firebase-PHP-JWT. В примере gitHub; я не мог понять

$ ключевую роль:

`$key = "example_key";` 

и

$ лексема часть:

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

Мои вопросы:

  1. Что должно быть $ key переменная?
  2. Почему & token переменная - это массив? Токен, который будет отправлен из мобильного приложения, является строкой.
  3. Если кто-то может опубликовать полный пример проверки идентификатора firebase с помощью PHP-JWT, я был бы признателен.

EDIT:

Окей я получил точку. Пример GitHub показывает, как генерировать код JWT (кодировать) и как его декодировать. В моем случае мне нужно только декодировать jwt, который закодирован firebase. Таким образом, мне нужно использовать только этот код:

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

В этом коде часть $ JWT является firebase ID маркера. Для $ ключевых переменная документация говорит:

Наконец, убедитесь, что ID маркер был подписан секретным ключом, соответствующим любому малышу лексема в. Возьмите открытый ключ от https://www.googleapis.com/robot/v1/metadata/x509/[email protected] и используйте библиотеку JWT для проверки подписи. Используйте значение max-age в заголовке Cache-Control ответа от этой конечной точки, чтобы узнать, когда обновлять открытые ключи.

Я не понимаю, как передать эти открытые ключи для декодирования функции.Ключи что-то вроде этого:

«----- BEGIN CERTIFICATE ----- \ nMIIDHDCCAgSgAwIBAgIIZ36AHgMyvnQwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE \ nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTcw \ nMjA4MDA0NTI2WhcNMTcwMjExMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl \ nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD \ nggEPADCCAQoCggEBANBNTpiQplOYizNeLbs + r941T392wiuMWr1gSJEVykFyj7fe \ nCCIhS/zrmG9jxVMK905KwceO/FNB4SK + l8GYLb559xZeJ6MFJ7QmRfL7Fjkq7GHS \ n0/sOFpjX7vfKjxH5oT65Fb1 + Hb4RzdoAjx0zRHkDIHIMiRzV0nYleplqLJXOAc6E \ n5HQros8iLdf + ASdqaN0hS0nU5aa/ПРОЦЕССОРЫ/EHQwfbEgYraZLyn5NtH8SPKIwZIeM7Fr \ ИПВ + SS7JSadsqifrUBRtb // fueZ/FYlWqHEppsuIkbtaQmTjRycg35qpVSEACHkKc \ nW05rRsSvz7q1Hucw6Kx/dNBBbkyHrR4Mc/wg31kCAwEAAaM4MDYwDAYDVR0TAQH/\ nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAW wCgYIKwYBBQUHAwIwDQYJ \ nKoZIhvcNAQEFBQADggEBAEuYEtvmZ4uReMQhE3P0iI4wkB36kWBe1mZZAwLA5A + U \ niEODMVKaaCGqZXrJTRhvEa20KRFrfuGQO7U3FgOMyWmX3drl40cNZNb3Ry8rsuVi \ nR1dxy6HpC39zba/DsgL07enZPMDksLRNv0dVZ/Х/wMrTLrwwrglpCBYUlxGT9RrU \ nf8nAwLr1E4EpXxOVDXAX8bNBl3TCb2fu6DT62ZSmlJV40K + wTRUlCqIewzJ0wMt6 \ NO8 + 6kVdgZH4iKLi8gVjdcFfNsEpbOBoZqjipJ63l4A3mfxOkma0d2XgKR12KAfYX \ ncAVPgihAPoNoUPJK0Nj + CmvNlUBXCrl9TtqGjK7AKi8 = \ п ----- END CERTIFICATE ----- \ п»

Должен ли я преобразовать этот открытый ключ в нечто, прежде чем передать его? Я попытался удалить все "\ n" и "----- НАЧАТЬ СЕРТИФИКАТ -----", "----- НАЧАТЬ СЕРТИФИКАТ -----" ... Но нет удачи. Тем не менее я получаю недопустимую ошибку подписи. Любой совет?

+0

Информация о версии Firebase? Обратите внимание, что вы не проверяете токены в PHP. Вы их набираете, отправляете их клиенту, а клиент выполняет проверку. – Kato

+0

@ Kato я использую последнюю версию. «Com.google.firebase: firebase-Auth: 10,0.1' . Я не понял. После входа клиента на мобильный, firebase auth возвращает токен. Я хочу проверить этот токен на стороне сервера с помощью PHP, чтобы убедиться, что токен, созданный firebase, или нет. Если проверка подтвердится, я разрешу клиенту. – eren130

ответ

6

HS256 используется, только если вы используете пароль для подписывания токена. Firebase использует RS256, когда он выдает токен, поэтому вам нужны открытые ключи из данного URL-адреса, и вам нужно установить алгоритм на RS256.

Также обратите внимание, что маркер, который вы получаете в своем приложении, не должен быть массивом, а строкой, которая состоит из трех частей: header, body и signature. Каждая часть отделяется ., таким образом, это дает вам простую строку: 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/<firebaseProjectID>
  • aud: <firebaseProjectID>
  • sub не является пустым

Так, например, вы можете проверить iss как это (где FIREBASE_APP_ID это идентификатор приложения из firebase консоли):

$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/[email protected]"); 
    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! 
+0

спасибо! Спасла мою жизнь ... последнее. Не могли бы вы отредактировать свой ответ и добавить образец кода, в котором хранятся общедоступные ключи в соответствии с «Cache-Control: max-age» и обновлять их, когда истекает максимальное время? Это будет полный источник выборки для проверки идентификатора Firebase. Извините за мое плохое знание PHP ... Кстати, уже два дня я играл с firebase auth. Заголовок «Cache-Control» из giveng url всегда был «max-age = 0». Итак, зачем нужно кэшировать? – eren130

+0

Я только что открыл его в Firefox и Chrome, и они показывают действительное значение максимального возраста. Как вы извлекаете заголовки? –

+0

Я использую расширение «Live HTTP Headers» в Firefox. Извините, только сейчас я видел, есть два отдельных заголовка: http://prntscr.com/e6ht8d – eren130

0

Рабочего пример https://stackoverflow.com/users/2644098/gergely-k%c5%91r%c3%b6ssy отличного ответа.

различие примечания:

  • испытан и работа

  • работы в средах неклассового

  • Больше кода, показывающий, как использовать его для Firebase (простой, один-лайнер отправьте код для проверки)

  • UnexpectedValueException охватывает всевозможные ошибки, которые могут возникнуть (s Уч утратившие/недействительные ключи)

  • Ну прокомментировал и простой в последующей

  • возвращает массив верифицированных данных из Firebase лексем (вы можете безопасно использовать эти данные для чего вам нужно)

ПРИМЕНЕНИЕ:

$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>; 

function get_userId_by_deviceToken($subToken) 
{ 
    //// CONNECT TO YOUR DB 
    //// RETURN THE USERID THAT MATCHES THE DEVICE/USER TOKEN 
} 

/////// 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 = false; 
       if (!empty($decoded->sub)) { 
        $userId = get_userId_by_deviceToken($decoded->sub); 
        $sub = !empty($userId); 
       } 
       if ($exp && $iat && $aud && $iss && $sub) { 
        // we have a confirmed Firebase user! 
        // build an array with data we need for further processing 
        $return['userId'] = $userId; 
        $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/[email protected]"); 
    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; 
}