最近在公司負責 Dashboard 的開發,除了從資料庫挖資料以外,還得想盡辦法從其他來源找到更多的資料,而其中一個資料來源就是 Google Analytics。

我過去都是在瀏覽器上使用 OAuth,這次花了一個下午才研究出如何在伺服器的 OAuth 用法,途中碰壁了好幾次,以下我將從頭到尾介紹如何接上 Google API,全文將以 Node.js 示範,其中原理可運用在其他程式語言,或 Google 其他服務的 API。

建立專案

首先,在 Google Developer Console 建立專案,並開啟「Analytics API」的存取權,到此為止都還是小菜一碟,跟不上的可以洗洗睡了,接下來才是正題。

為了獲得 API 的存取權,我們必須申請一個新的用戶端 ID。請進入側邊欄中的「API 和驗證」→「憑證」頁面,你將可以看到一個預先建立的「Compute Engine 和 App Engine」的用戶端 ID,那個一點屁用都沒有,別管它。

點選橘色按鈕「建立新的用戶端 ID」後,會跳出一個視窗,請選擇「服務帳戶」,並按下藍色按鈕「建立用戶端 ID」。

經過數秒後,新的用戶端 ID 便建立完成,同時會跳出一個 JSON 檔案的下載視窗,該檔案是用來存放 Private key 的,非常重要,請妥善保存。

服務帳戶到此為止便建立完成,你可在頁面下方看到熱騰騰剛建好的服務帳戶,你現在可以關掉這個頁面了,因為稍後的操作都會圍繞在剛剛下載的 JSON 檔案;如果你不小心遺失了 Private key,可以使用「Generate new JSON key」按鈕建立一組新的 Public/Private key。

OAuth 認證

Google API 採用 OAuth 2.0 認證,然而認證方式與我們平常所熟悉的方式不同,現在許多網站上都會有「以 Google 帳號登入」的按鈕,依照簡易的操作步驟即可認證;不過在伺服器上可沒有按鈕讓你按,於是我們必須透過剛剛申請的服務帳戶進行認證。

上圖來自 Google Developers,完美解釋了接下來的流程,首先,我們必須建立 JWT(JSON Web Token),並使用 JWT 向 Google 索取 Token,之後方可存取 Google API。

一個完整的 JWT 應具備以下內容:

{Base64url encoded header}.{Base64url encoded claim set}.{Base64url encoded signature}

第一部分是 Header,此部份用來指示 JWT 所使用的演算法及類型,在存取 Google API 時,我們使用 RSA SHA256 演算法,因此內容為:

{
  "alg": "RS256",
  "typ": "JWT"
}

第二部分是 Claim set,此部份是 JWT 的主要資料部分,內容如下:

{
   "iss": "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com",
   "scope": "https://www.googleapis.com/auth/analytics.readonly",
   "aud": "https://accounts.google.com/o/oauth2/token",
   "iat": 1328550785,
   "exp": 1328554385
}
名稱 說明
iss Client Email,即為剛剛下載的 JSON private key 中的 client_email 欄位。
scope 應用程式所請求的資料範圍,因為我們需要取得 Google Analytics 的資料,因此為 https://www.googleapis.com/auth/analytics.readonly
aud 認證目標。在此為 https://accounts.google.com/o/oauth2/token
iat 此請求的發起時間(秒)。
exp Token 的過期時間(秒),最大時間為 iat(發起時間)的 1 小時後。

在介紹第三部分之前,請先將前兩部分的資料以 Base64 方式編碼,並以 . 串接。如果你不知道怎麼在 Node.js 內進行 Base64 編碼,可參考以下程式碼:

new Buffer(str).toString('base64');

第三部分是 Signature,即是將前兩部分的編碼字串以 Private key 加密後的結果,你可從剛剛下載的 JSON private key 中的 private_key 欄位取得 Private key,並參考以下的程式碼取得加密字串。

var crypto = require('crypto');

crypto.createSign('sha256').update(jwt).sign(privateKey, 'base64');

完成後,以 . 串接所有部分就是一個正確的 JWT 了。接著請使用 JWT 向 Google 索取 Token,POST 到 https://accounts.google.com/o/oauth2/token ,並加上以下資料:

名稱 說明
grant_type 認證類型。在這裡為 urn:ietf:params:oauth:grant-type:jwt-bearer,別忘了 URL 編碼。
assertion 就是剛剛建立的 JWT。

以下使用 request 為例:

var request = require('request'),
  querystring = require('querystring');

request.post('https://accounts.google.com/o/oauth2/token', {
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  body: querystring.stringify({
    grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
    assertion: jwt
  }),
  encoding: 'utf8'
}, function(err, res, body){
  // ...
});

若所有資料正確無誤的話,應該可得到以下回應:

{
  "access_token" : "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M",
  "token_type" : "Bearer",
  "expires_in" : 3600
}

其中 access_token 就是我們需要的 Token,而 expires_in 代表這個 Token 的有效時間(秒);簡而言之,這個 Token 的有效時間只有 1 小時(3600 秒)。

取得資料

一旦取得 Token 後,一切都變得簡單了,但是別忘了把閱覽權限開放給服務帳戶的 Client Email,否則將無法透過 API 存取資料。

此外,還必須取得「資源數據編號」,別搞錯,這個可不是追蹤編號喔!完成後,使用以下方式即可取得資料。

Authorization: Bearer {oauth2-token}

GET https://www.googleapis.com/analytics/v3/data/ga
  ?ids=ga:{id}
  &start-date=2008-10-01
  &end-date=2008-10-31
  &metrics=ga:sessions,ga:bounces

後記

這篇文章最難的部份大概就是如何產生出 JWT,其他部分就只是向 Google 請求資料而已,沒什麼好解釋的,所以我接下來就寫些廢話吧。

這季有一部雖然看起來很普通,而且連名稱也有「普通」這詞的動畫《普通の女子校生が【ろこどる】やってみた》,我原本以為這只是一部普通的地區推銷動畫,沒想到女二卻是一名サイコレズ!如果各位喜歡百合的話,請務必要看看這部動畫!

週末時我花了一小時的時間把筆電升級到 Yosemite Beta,接下來我大概會寫一篇文章講述關於此系統的心得;7 月 22 日起,.moe 網域開放一般註冊,於是我買了 maji.moe,未來大概會開放子網域的申請服務,目前正在研究如何利用 Go 語言架站。