Ajax 板


LINE

雖然標題是寫 sign with P12 Key , 不過其實內文是要寫如何 pass google OAuth with JWT 。 其中有一個 step 是 sign with P12 Key, 因為 Google OAuth 給的 Key 是 PKCS 12 的, 如何正確的用他進行簽章這件事情我走了不少冤枉路。 最近搞這東西搞了超過16小時,雖然說是自己做著玩得 project , 但被認證這麼基本的東西擋在門外,整個是很焦慮。XD --------------------------------- 這一切的起源都是因為我想用 NodeJS 連 Google Calendar API, 自己新增跟更新特定 Calendar 上的 Calendar Event 。 --------------------------------- --------------------------------- 我們走 Google OAuth 2.0 ,說到 OAuth 有跟 FB API 打交道過的, 應該都不會太陌生,你需要給他 scope 跟一些有的沒的相關資料, 然後你會取得一個 accessToken ,接著你就可以拿這 accessToken 去作壞事。 讀完 API,首先當然就是要先取得 accessToken 首先我先讀了 Google Developer Doucment , 其中有好幾種作法。(詳情看下面連結的左側選單) http://goo.gl/qUZEL 後來決定採用 Service Account,也就是說我自己 server 就是個 user , 自己跟 server 要 accessToken ,而不是讓第三方使用者, 自己導向他們網站驗證取得 accessToken 的作法。 (理由單純是因為想自己測試用,要驗證很煩; 另一方面是想這種作法他們是怎麼搞驗證的。 ) --------------------------------- @首先先到 Google API Console 申請一個 API Key 先 Create Proejct ,然後選 API Access , 然後選擇開 client、類別選 Service account。 https://code.google.com/apis/console/b/0/ 接著你就會拿到一個 Google 說他們不會保存的 Private key file, 是 PKCS 12 結尾的(副檔名 .p12 )。 另外你會拿到一個密碼(key password),要記住有這回事,後面會用到, 這個密碼叫做 "notasecret" ,應該是固定的。 這個檔案要好好保存,掉了的話就要重新申請一把了。 然後你會看到這些資訊, http://screencast.com/t/Q8rDbn5AR 包含 ClientID,Email Address ,Public key fingerprints, 其中我們會用到的主要是 Email Address。 萬一你把 P12 Key 弄丟了也可以來這右邊點選 Generate new key。 @JWT 前面的文件其中提到要組一個叫 JWT 的東西,什麼是 JWT 呢, 他全名叫 JSON Web Token ,主要就是用來作線上驗證用的。 目前採用他的,根據我這兩天 Google 看到的,除這個 google OAuth 以外, 還有 Apple 的 In App Purchase 跟 Google 的電子錢包等。 JWT 到底是東西,我一開始根本就沒理他,反正欄位文件都有了,我就照著填, 我就是玩遊戲不看說明書的那種人啦(咦), 不過有興趣的人還是可以詳閱文件,瞭解其為何可以作為安全性的驗證。 http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html @先試著照文件組組看 JWT 文件還是這篇 http://goo.gl/qUZEL A JWT is composed as follows: {Base64url encoded header}.{Base64url encoded claim set}.{Base64url encoded signature} 注意其中有一個點(.),另外這裡都是 Base64 編碼。 @JWT Header JWT Header 在 Google OAuth 上是固定的,寫死的, 這個文件上有給了,不用懷疑照著作就對了。 var header = JSON.stringify({ "alg": "RS256", "typ": "JWT" }); 我是偷懶用 JSON.stringify 直接轉字串,方便閱讀也方便修改。 接著注意到他要做 Base64 Encode,對字串要做 base64 轉換很簡單, 透過 Buffer 作就好。 new Buffer(header).toString("base64") @JWT Claim Set 一樣,照著文件寫,這裡開始有玄機了。 var claims = JSON.stringify({ "aud": "https://accounts.google.com/o/oauth2/token", //到期時間(單位是秒,所以getTime 是 ms 要除 1000), //加上 3600 是指一小時以後過期,這是 Google 允許的最大時間 "exp": parseInt(new Date().getTime()/1000,10) +3600, //申請時間 "iat": parseInt(new Date().getTime()/1000,10), //申請者,這裡請用 Api console 那邊看到的 email address "iss": "[email protected]", //Scope ,看你想申請用什麼服務就寫什麼 //這個不太好找,請自行努力,這裡只附上 Calendar 的 scope。 //我是從 http://goo.gl/7pbtn 下面 try it, //右方有 Authr using OAuth 2.0 點下去看到的。 "scope":"https://www.googleapis.com/auth/calendar" }); 照理說 Claim Set 應該沒問題,只是填資料而已,要特別小心手誤, 很多人會把 iss 填成 client id ,要填的是前面取得的 email address。 一樣要做 Base64 Encoded。 new Buffer(claims).toString("base64") @Computing the Signature 這個才是大魔王,非常麻煩的東西,也是本文重點, 他要先把前面做出來的兩個元素,透過你取得的 p12 Key 進行簽章(sign)。 幾件事情要注意: 1.用來簽章的是已經 base64 encode 的 header 跟 claims, 別傻傻的用原本的 json 字串來簽。 2.header 跟 claims 中間有一個點(.),但結尾沒有點。 var content = encodedHeader+"."+encodedClaims; 這裡步驟比較複雜,我們一步一步來: A.安裝 crypto ,這樣你才能進行加解密相關操作。 B.你拿到的是 PKCS 12 (P12) 的 key , 很不幸的是 nodejs 似乎沒有直接處理 p12 的 client,至少我沒搜到。 所以你得先找台有 open ssl 的機器(linux base的通常都有), 先把 private key 的部份以 RSA 的形式繪出。 (或者傻瓜點的講法,把 P12 轉 Pem。) 指令是 openssl pkcs12 -in my-privatekey.p12 -out privatekey.pem -nodes -nodes 是指建立 privatekey 時,不需要再用 passphase進行加密。 把 my-privatekey.p12 改成你從 google 拿到的那個檔案的檔名, privatekey.pem 則是即將建立的 pem 檔案的檔名。 這時候他會問你 password ,請輸入 "notasecret", 順利的話,這時你會取得 privatekey.pem C.簽章 這件事情很單純,透過 crypto 就行了,範例碼附於後, 但要記得先把 prviatekey.pem 放到同資料夾下: var fs = require("fs"), crypto = require("crypto"); function sign(content) { var key = fs.readFileSync('privatekey.pem');//讀pk,直接當key用 var sign = crypto.createSign('RSA-SHA256');//指定演算法 sign.update(content); //你要簽的內容 var sig = sign.sign(key,'base64'); //進行簽章動作,並指定為 base64輸出 return (sig); } var signed = sign(content); //把之前組好的 header 跟 claims 拿來簽, //最後再把相關資料一起加起來就大功告成了。 var jwt = content +"."+signed; //到這一步就可以先驗證 JWT 是否能正確讀取 //https://developers.google.com/in-app-payments/docs/jwtdecoder?hl=zh-tw 最後再作 Post 給 'https://accounts.google.com/o/oauth2/token' 這裡我用的是 restler ,我想這應該對寫 node 的人, 不會有太大障礙才對,也可以試試自己用的方法。 var rest = require('restler'); rest.post('https://accounts.google.com/o/oauth2/token', { data:{ grant_type:"urn:ietf:params:oauth:grant-type:jwt-bearer", assertion:jwt } }).on("complete", function(data, response) { console.log(data); //順利的話會看到 access_token ,不然就是錯誤訊息 }); btw base64 Url Safe 的處理,作不作都無所謂, 我測過了都會過,post 通常不需要處理這個。 ------------------------------------------------- 過關之後就會覺得好像很簡單,順便寫一下我卡關卡在哪: 1.找怎麼 sign 這件事情 google 很久,才找到一個可以用的範例。XD 然後我其實是一直覺得應該會有人寫好 p12 client 的, 不是很想自己作轉換 pem 的動作, 加上我又懷疑 pem 轉過去後,是不是會有變化導致我沒簽過。 這件事情一直到後來我去找 Google API Java Client , 直接看他在 Java 世界怎麼作跟怎麼發,用他作為對照組交叉測試很多次之後, 才確定 P12 裡面的 PK 跟轉出來的 pem 是同樣的無誤。 找 pk12 client 這件事情花不少時間, 然後一直找到 node tls 的參考資料,但那完全就是不同事。Orz 2.我一直不確定 SHA256withRSA 是不是等同於 RSA-SHA256 ,這是因為基礎知識不足。 3.我打從一開始就犯一個要命的致命錯誤,因為我寫一個函式幫助我轉換 base64, function toBase64(obj) { return new Buffer(obj).toString("base64"); } 然後前面兩個 claim 跟 header 運作很正常, 測出來的資料也就對,我就太相信他了。 另一方面是我第一個找到的 sign 的 sample 是這樣的 var key = fs.readFileSync('privatekey.pem');//讀pk,直接當key用 var sign = crypto.createSign('RSA-SHA256');//指定演算法 sign.update(content); //你要簽的內容 var sig = sign.sign(key); return (sig); 我根本沒注意到說這裡可以改成出 base64, 另一方面因為我知道 buffer 也可以用來接 binary, 我想說好吧,那就寫 var sig = toBase64(sign.sign(key)); 從這裡開始就全錯了......Orz 不是不能這樣寫,只是如果要這樣作的話,要明確指定進入的型別, 像是 var sig = new Buffer(sign.sign(key),"binary").toString("base64"); 這樣就會對。 可是最要命的事情是他還是可以產出 base64 的結果, 我猜大概是拿 toString() 的結果直接去做的吧。 我錯過這個檢核點非常多次,一直懷疑是 JWT content 的問題, 最後我終於發現是 sign 的 issue , 我用 Java 世界可以正常通過的 code 直接簽 "hello world", 過來 nodejs 簽 "hello world" 發現不一致, 過來交叉測試大概花了三個小時,才發現這個低級錯誤。 學到的教訓就是 Buffer 進出的型別要講得很清楚, 不然就會出這種無聲的錯誤。Orz 網路上教學如何用 NodeJS 作 JWT 的資料真的很少, 希望這個資料可以對大家有幫助。囧rz -- P12 = PKCS 12 ,是 public key + private key + password 的 keystore; PEM = X.509 cert ,public key + private key 的 key pair 其中 key 分為 RSA key 跟 DSA key 等不同類型。 -- Life's a struggle but beautiful. --



※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 1.34.116.11 ※ 編輯: TonyQ 來自: 1.34.116.11 (11/17 15:35)
1F:推 dreamseer:快拜! 11/17 15:39
※ 編輯: TonyQ 來自: 1.34.116.11 (11/17 15:51)
2F:推 skyman1999:超強,,, 11/17 23:47
3F:推 liaosankai:快M起來 lol 11/24 12:26







like.gif 您可能會有興趣的文章
icon.png[問題/行為] 貓晚上進房間會不會有憋尿問題
icon.pngRe: [閒聊] 選了錯誤的女孩成為魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一張
icon.png[心得] EMS高領長版毛衣.墨小樓MC1002
icon.png[分享] 丹龍隔熱紙GE55+33+22
icon.png[問題] 清洗洗衣機
icon.png[尋物] 窗台下的空間
icon.png[閒聊] 双極の女神1 木魔爵
icon.png[售車] 新竹 1997 march 1297cc 白色 四門
icon.png[討論] 能從照片感受到攝影者心情嗎
icon.png[狂賀] 賀賀賀賀 賀!島村卯月!總選舉NO.1
icon.png[難過] 羨慕白皮膚的女生
icon.png閱讀文章
icon.png[黑特]
icon.png[問題] SBK S1安裝於安全帽位置
icon.png[分享] 舊woo100絕版開箱!!
icon.pngRe: [無言] 關於小包衛生紙
icon.png[開箱] E5-2683V3 RX480Strix 快睿C1 簡單測試
icon.png[心得] 蒼の海賊龍 地獄 執行者16PT
icon.png[售車] 1999年Virage iO 1.8EXi
icon.png[心得] 挑戰33 LV10 獅子座pt solo
icon.png[閒聊] 手把手教你不被桶之新手主購教學
icon.png[分享] Civic Type R 量產版官方照無預警流出
icon.png[售車] Golf 4 2.0 銀色 自排
icon.png[出售] Graco提籃汽座(有底座)2000元誠可議
icon.png[問題] 請問補牙材質掉了還能再補嗎?(台中半年內
icon.png[問題] 44th 單曲 生寫竟然都給重複的啊啊!
icon.png[心得] 華南紅卡/icash 核卡
icon.png[問題] 拔牙矯正這樣正常嗎
icon.png[贈送] 老莫高業 初業 102年版
icon.png[情報] 三大行動支付 本季掀戰火
icon.png[寶寶] 博客來Amos水蠟筆5/1特價五折
icon.pngRe: [心得] 新鮮人一些面試分享
icon.png[心得] 蒼の海賊龍 地獄 麒麟25PT
icon.pngRe: [閒聊] (君の名は。雷慎入) 君名二創漫畫翻譯
icon.pngRe: [閒聊] OGN中場影片:失蹤人口局 (英文字幕)
icon.png[問題] 台灣大哥大4G訊號差
icon.png[出售] [全國]全新千尋侘草LED燈, 水草

請輸入看板名稱,例如:Soft_Job站內搜尋

TOP