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灯, 水草

请输入看板名称,例如:Tech_Job站内搜寻

TOP