顯示具有 token 標籤的文章。 顯示所有文章
顯示具有 token 標籤的文章。 顯示所有文章

2017年10月27日 星期五

Firebase 開發筆記 - 使用 PHP 之 Verify ID tokens using a third-party JWT library

PHP 漸漸地越來越沒有愛了 XD Ubuntu server 預設還是停留在 php5.5 系列,而許多套件都進入了 PHP 7 世界了。不知是不是這樣子,所以官方沒再推出 PHP SDK ?於是乎,只好自己刻一下下。也順勢認識 JWT,第一次認識是在 APNs Auth Key 的使用,沒想到這麼快又要上戰場了 XD

網路資源:
若覺得很煩,就直接用 https://github.com/kreait/firebase-php 吧!這篇專注在紀錄 JWT - Firebase 的使用和驗證。

用法:

$ mkdir jwt-3.2.2 ; cd jwt-3.2.2 ; composer require lcobucci/jwt
$ vim test.php
<?php
require 'vendor/autoload.php';

$token_string = 'XXXXXXXXXXX';

$signer = new Lcobucci\JWT\Signer\Rsa\Sha256();
$keychain = new Lcobucci\JWT\Signer\Keychain;
$parser = new Lcobucci\JWT\Parser;

try {
$token = $parser->parse($token_string);
} catch( Exception $e ) {
echo "decode failed\n";
exit;
}

// check key field
foreach (array( 'iat', 'exp', 'aud', 'iss', 'user_id' ) as $field) {
if (!$token->hasClaim($field)) {
echo "no $field\n";
exit;
}
}

if ($token->isExpired()) {
echo "exp expired\n";
exit;
}

if (!$token->hasHeader('kid')) {
echo "no kid\n";
exit;

}
$kid = $token->getHeader('kid');

$keys = json_decode(file_get_contents('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'), true);

if (!isset($keys[$kid])) {
echo "kid not found\n";
exit;
}


try {
if( $token->verify($signer, $keychain->getPublicKey($keys[$kid])) )
echo "PASS\n";
} catch (Exception $e) {
echo "Failed";
}

$user_id = $token->getClaim('user_id');


連續動作(PHP Codeigniter Usage):

$ cat Jwt_library.php
<?php
require_once 'jwt-3.2.2/vendor/autoload.php';
class Jwt_library {
public function __construct() {
$this->ci = &get_instance();
$this->parser = new Lcobucci\JWT\Parser;
$this->keychain = new Lcobucci\JWT\Signer\Keychain;
$this->signer = new Lcobucci\JWT\Signer\Rsa\Sha256;
}

public function verify_firebase_token(&$error, $user_token, $project_id, $service_public_keys = array()) {
$user_id = NULL;
$error = NULL;
$token = false;
try {
$token = $this->parser->parse($user_token);
} catch (Exception $e) {
$error= 'decode failed';
return NULL;
}

// check filed name
foreach (array( 'iat', 'exp', 'aud', 'iss', 'user_id' ) as $field) {
if (!$token->hasClaim($field)) {
$error = "no $field";
return NULL;
}
}

if ($token->isExpired()) {
$error = 'exp expired';
return NULL;
}
//if ($token->getClaim('iat') > time() )
// return 'token has been issued in the future';

if ($token->getClaim('aud') != $project_id ) {
$error = 'aud error';
return NULL;
}

$user_id = $token->getClaim('user_id');

$kid = false;
try{
$kid = $token->getHeader('kid');
} catch (Exception $e) {
$error = 'no kid';
return NULL;
}

if (!isset($service_public_keys[$kid])) {
$error = 'kid not found: '.$kid;
return NULL;
}

try {
if ($token->verify($this->signer, $this->keychain->getPublicKey($service_public_keys[$kid])))
return $user_id;
} catch (Exception $e) {
$error = 'verify failed';
}

return NULL;
}
}


$ cat php_ci_controller.php
<?php
$output = array( 'status' => false );

$authorization_info = $this->input->get_request_header('Authorization', True);
if (empty($authorization_info) || strstr($authorization_info, 'Bearer ') == false) {
        $output['error'] = -1;
        $output['message'] = 'no Authorization';
        $this->_json_output($output);
        return;
}

$firebase_user_token = trim(substr($authorization_info, strlen('Bearer ')));
$this->load->library('jwt_library');
$keys = @json_decode(@file_get_contents('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'), true);
$firebase_user_id = $this->jwt_library->verify_firebase_token($verify_error, $firebase_user_token, 'YourFirebaseProjectId', $keys);
if (empty($firebase_user_id)) {
$output['error'] = 1;
$output['message'] = 'verify: '.$verify_error;
$this->_json_output($output);
return;
}

2014年11月25日 星期二

[PHP] CodeIgniter 與 Facebook PHP SDK v4 整合筆記 @ Ubuntu 14.04

整個流程並主要是把 Facebook PHP sdk v4 source code 下載到 ci_proj/application/libraries 中,接著撰寫一隻 ci library: facebook.php 來橋接一下,就差不多可以收工了。在此謹驗證收到的 facebook token 是否有效,其他部分並不實作:

$ cd /path/ci_proj/application/libraries
$ git clone https://github.com/facebook/facebook-php-sdk-v4.git
$ ln -s facebook-php-sdk-v4 facebook


建立 Facebook 相關 config:

$ vim /path/ci_proj/application/config/facebook.php
<?php defined('BASEPATH') OR exit('No direct script access allowed');
$config['facebook']['api_id'] = 'FACEBOOK APP ID';
$config['facebook']['app_secret'] = 'FACEBOOK APP SECRET KEY';


編輯橋接的 library 用法:

$ vim /path/ci_proj/application/libraries/facebook.php

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
require_once APPPATH . 'libraries/facebook/autoload.php';

use Facebook\FacebookRedirectLoginHelper;
use Facebook\FacebookSession;
use Facebook\FacebookRequest;
use Facebook\GraphUser;
use Facebook\FacebookRequestException;

class Facebook {
var $ci;
var $helper;
var $session;
var $permissions;

public function __construct() {
$this->ci =& get_instance();
$this->ci->load->config('facebook');
$this->permissions = $this->ci->config->item('permissions', 'facebook');
FacebookSession::setDefaultApplication( $this->ci->config->item('api_id', 'facebook'), $this->ci->config->item('app_secret', 'facebook') );
        }
public function token_validation($token) {
try {
$this->session = new FacebookSession( $token );
if ( $this->session->validate() )
return true;
} catch ( Exception $e ) {
}
$this->session = null;
return false;
}
public function get_user($token = NULL) {
if ( $this->session || (!empty($token) && $this->token_validation($token)) ) {
$request = ( new FacebookRequest( $this->session, 'GET', '/me' ) )->execute();
$user = $request->getGraphObject()->asArray();
return $user;
}
return false;
}
}


使用方式:

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Test extends CI_Controller {
public function index()
{
$this->load->config('facebook');
$this->load->library('facebook');
$output = array(
'reqeusts' => $_REQUEST,
'app_id' => $this->config->item('api_id', 'facebook'),
'user_id' => $this->facebook->get_user($_REQUEST['token'])
);
$this->output->set_content_type('application/json')->set_output(json_encode($output));
        }
}

2014年3月14日 星期五

Android 開發筆記 - 使用 Google Cloud Messaging for Android (GCM)

原來 Android Cloud to Device Messaging Framework (C2DM) has been officially deprecated as of June 26, 2012 ... 現在改用 Google Cloud Messaging for Android
Google Cloud Messaging for Android (GCM) is a free service that helps developers send data from servers to their Android applications on Android devices, and upstream messages from the user's device back to the cloud. This could be a lightweight message telling the Android application that there is new data to be fetched from the server (for instance, a "new email" notification informing the application that it is out of sync with the back end), or it could be a message containing up to 4kb of payload data (so apps like instant messaging can consume the message directly). The GCM service handles all aspects of queueing of messages and delivery to the target Android application running on the target device.
一遍後,其實 GCM 的架構跟 Apple Push Notification service (APNs) 很像,主要都是先由 Mobile device 跟 Android server (Apple server) 註冊一個 token,接著再請 Mobile device 將此 token 交給 Service Provide。而 Service Provider 就利用這個 token 跟 Mobile device 傳訊息。比較不一樣的是 GCM 提供的架構多了一些設計,詳細請參考官網資料,在此僅簡易筆記一下。

Google Developer console:
  1. 首先,先到 Google Developer Console 建立一個 project,可得到 Service Provider ID
  2. 啟用 GCM service
  3. 建立 keys,APIs & auth > Credentials > Create a new key > Server key  (測試上 IP 設定為 0.0.0.0/0)
  4. 得到一組 API key,之後就是透過這個 key 跟 Mobile device's token 丟訊息
Android app:
  1. 下載 Google Play service library 並安置好
  2. 在 AndroidManifest.xml 宣告相關權限、並新增 <receiver> 來接收 Service Provider 的訊息
  3. 接著就是程式啟動時,透過 Google library 跟 GCM 註冊一個 token,此時需要指定 Service Provider ID 資訊
  4. 將 token 交給 Service Provider,如此一來 service provider 就可以傳訊息
  5. 實作好 <receiver>,當訊息進來時,啟動一個 service 發 notification 出去
接著是一些片段範例:
  • Service Provider ID: Your-Sender-ID
  • API KEY: API_Key
  • Android App Package Name: com.example.gcm
  • Mobile device token: MobileDeviceToken
  • Message:hello world
  • Payload: {"to":"MobileDeviceToken","data":{"message":"hello world","action":"com.example.gcm"}}
Service Provider 發訊息給指定的 Mobile device:

$ curl --header "Authorization: key=API_Key" --header Content-Type:"application/json" https://android.googleapis.com/gcm/send  -d '{"to":"MobileDeviceToken","data":{"message":"hello world","action":"com.example.gcm"}}'

Android app - AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.gcm"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="15" />

    <!--  ADD FOR Google Cloud Messaging ### Begin -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:name="com.example.gcm.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
<uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />
    <!--  ADD FOR Google Cloud Messaging ### End -->


    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.gcm.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
   
        <!--  ADD FOR Google Cloud Messaging ### Begin -->
        <meta-data android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />
        <receiver
            android:name=".GcmBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="com.example.gcm" />
            </intent-filter>
        </receiver>
        <service android:name=".GcmIntentService" />
        <!--  ADD FOR Google Cloud Messaging ### End -->

   
    </application>
</manifest>


Android app - Receiver:

package com.example.gcm;

import com.google.android.gms.gcm.GoogleCloudMessaging;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.util.Log;

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
private static final String TAG = "GCM";

@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Log.i(TAG, "GcmBroadcastReceiver");

GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
String messageType = gcm.getMessageType(intent);
Log.i(TAG, "GcmBroadcastReceiver messageType:"+messageType);

Bundle extras = intent.getExtras();
if (!extras.isEmpty()) {
if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {

} else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {

} else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {

}
Log.i(TAG, "GcmBroadcastReceiver Received: " + extras.toString());
}
}
}


Android app - MainActivity:

public class MainActivity extends ActionBarActivity {
private static final String TAG = "GCM";
public static final String EXTRA_MESSAGE = "message";
public static final String PROPERTY_REG_ID = "registration_id";
private static final String PROPERTY_APP_VERSION = "appVersion";
private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
String SENDER_ID = "Your-Sender-ID";

GoogleCloudMessaging gcm;
AtomicInteger msgId = new AtomicInteger();
String regid;
Context context;

private boolean checkPlayServices() {
   int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
   if (resultCode != ConnectionResult.SUCCESS) {
       if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
           GooglePlayServicesUtil.getErrorDialog(resultCode, this,
                   PLAY_SERVICES_RESOLUTION_REQUEST).show();
       } else {
           Log.i(TAG, "This device is not supported.");
           finish();
       }
       return false;
   }
   return true;
}

private String getRegistrationId(Context context) {
   final SharedPreferences prefs = getGCMPreferences(context);
   String registrationId = prefs.getString(PROPERTY_REG_ID, "");
   if (registrationId.isEmpty()) {
       Log.i(TAG, "Registration not found.");
       return "";
   }
   // Check if app was updated; if so, it must clear the registration ID
   // since the existing regID is not guaranteed to work with the new
   // app version.
   int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
   int currentVersion = getAppVersion(context);
   if (registeredVersion != currentVersion) {
       Log.i(TAG, "App version changed.");
       return "";
   }
   return registrationId;
}

private SharedPreferences getGCMPreferences(Context context) {
   // This sample app persists the registration ID in shared preferences, but
   // how you store the regID in your app is up to you.
   return getSharedPreferences(MainActivity.class.getSimpleName(),
           Context.MODE_PRIVATE);
}

private static int getAppVersion(Context context) {
   try {
       PackageInfo packageInfo = context.getPackageManager()
               .getPackageInfo(context.getPackageName(), 0);
       return packageInfo.versionCode;
   } catch (NameNotFoundException e) {
       // should never happen
       throw new RuntimeException("Could not get package name: " + e);
   }
}
private void registerInBackground() {
new AsyncTask<Object, Object, Object>() {

@Override
protected Object doInBackground(final Object... arg0) {
// TODO Auto-generated method stub
String msg = "";
           try {
               if (gcm == null) {
                   gcm = GoogleCloudMessaging.getInstance(context);
               }
               regid = gcm.register(SENDER_ID);
               msg = "Device registered, registration ID=" + regid;

               // You should send the registration ID to your server over HTTP,
               // so it can use GCM/HTTP or CCS to send messages to your app.
               // The request to your server should be authenticated if your app
               // is using accounts.
               sendRegistrationIdToBackend();

               // For this demo: we don't need to send it because the device
               // will send upstream messages to a server that echo back the
               // message using the 'from' address in the message.

               // Persist the regID - no need to register again.
               storeRegistrationId(context, regid);
           } catch (IOException ex) {
               msg = "Error :" + ex.getMessage();
               // If there is an error, don't just keep trying to register.
               // Require the user to click a button again, or perform
               // exponential back-off.
           }
           return msg;
}

}.execute(null, null, null);
}

private void sendRegistrationIdToBackend() {
   // Your implementation here.
}

private void storeRegistrationId(Context context, String regId) {
   final SharedPreferences prefs = getGCMPreferences(context);
   int appVersion = getAppVersion(context);
   Log.i(TAG, "Saving regId on app version " + appVersion);
   Log.i(TAG, "Saving regId: " + regId);
   SharedPreferences.Editor editor = prefs.edit();
   editor.putString(PROPERTY_REG_ID, regId);
   editor.putInt(PROPERTY_APP_VERSION, appVersion);
   editor.commit();
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

        context = getApplicationContext();

        // Check device for Play Services APK. If check succeeds, proceed with
        //  GCM registration.
        if (checkPlayServices()) {
            gcm = GoogleCloudMessaging.getInstance(this);
            regid = getRegistrationId(context);

            if (regid.isEmpty()) {
                registerInBackground();
            }
        } else {
            Log.i(TAG, "No valid Google Play Services APK found.");
        }

if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
}
// ...
}


更完整的範例請參考官網:GCM Client