#まとめ
Requestsを使わずurllib等のpython2.7の標準ライブラリのみでOAuth1.0の署名付きリクエストを送信する関数を書きました。この関数でTwitterのRestAPIを使いツイートしました。
#なぜ実装した?
RequestsやTweepyなどは依存関係が複雑でGoogle App Engineに搭載できなかったから
#にゃーんと呟く
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import urllib2
import time, random
import hmac, hashlib, base64
from urllib import quote, urlencode
#リクエストを送信する関数
# method: "POST" or "GET"
# url: e.g. "https://www.w3.org/History/19921103-hypertext/hypertext/WWW/TheProject.html"
# param: e.g. {"status":"にゃーん"}
# header: e.g. {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64)"}
#
# return: {"code":200,"body":"something"}
def request(method, url, param, header):
method = method.upper()
param = urlencode(param)
if method == "POST":
pass
if method == "GET":
url += "?" + param
param = None
try:
req = urllib2.Request(url, param, header)
handler = urllib2.urlopen(req)
return {"body": handler.read(), "code": handler.getcode()}
except urllib2.HTTPError as e:
print(e.fp.read())
#OAuth1.0の署名付きリクエストを送信する関数
# consumer_key: Consumer Key (API Key)
# consumer_secret: Consumer Secret (API Secret)
# access_token: Access Token
# access_token_secret: Access Token Secret
# method: e.g. "POST"
# url: e.g. https://api.twitter.com/1.1/statuses/update.json
# param: {"status":にゃーん}
#
# return {"code":200,"body":"something"}
def oauth10(consumer_key, consumer_secret, access_token, access_token_secret, method, url, param):
for k,v in param.items():
if isinstance(v,unicode):
v=v.encode("utf-8")
param[k]=str(v)
baseparam = {
"oauth_token": access_token,
"oauth_consumer_key": consumer_key,
"oauth_signature_method": "HMAC-SHA1",
"oauth_timestamp": str(int(time.time())),
"oauth_nonce": str(random.getrandbits(64)),
"oauth_version": "1.0"
}
signature = dict(baseparam)
signature.update(param)
signature = '&'.join('{0}={1}'.format(quote(key, ''), quote(signature[key], '~')) for key in sorted(signature))
signature = ("{0}&{1}".format(consumer_secret, access_token_secret), '{0}&{1}&{2}'.format(method.upper(), quote(url, ''), quote(signature, '')))
signature = base64.b64encode(hmac.new(signature[0], signature[1], hashlib.sha1).digest())
header = dict(baseparam)
header.update({"oauth_signature": signature})
header = ",".join("{0}={1}".format(quote(k, ''), quote(header[k], '~')) for k in sorted(header))
header = {"Authorization": 'OAuth {0}'.format(header)}
return request(method, url, param, header)
consumer_key="ひみつ"
consumer_sec="ひみつ"
access_key="ひみつ"
access_sec="ひみつ"
r=oauth10(consumer_key,consumer_sec,access_key,access_sec,"POST","https://api.twitter.com/1.1/statuses/update.json",{"status":"にゃーん"})
print(r["code"])#200
print(r["body"])#{"created_at":"Sat May 19 06:42:23 +0000 2018","id":9977291606~~
にゃーん
— チャップリン (@misumi3104) 2018年5月19日
#大まかな手順
0.用意するもの
認証に必要な情報 | 説明 |
---|---|
consumer_key | Consumer Key (API Key) |
consumer_secret | Consumer Secret (API Secret) |
access_token | Access Token |
access_token_secret | Access Token Secret |
method | RESTAPIに指定されている送信方法。今回はPOST |
url | APIのURL "https://api.twitter.com/1.1/statuses/update.json" |
param | APIのパラメータ。今回はつぶやく内容を指定。{"status":"にゃーん"} |
1.基本的なパラメータを集める
認証に必要な情報 | 説明 |
---|---|
oauth_token | アプリケーションの利用者を示す文字列 |
oauth_consumer_key | アプリケーションを示す文字列 |
oauth_signature_method | 署名に用いるハッシュ関数を示す文字列 |
oauth_timestamp | UNIX時刻 |
oauth_nonce | リクエストごとに固有の文字列。なんでもいい。 |
oauth_version | OAuth1.0を使うので"1.0" |
baseparam = {
"oauth_token": access_token,
"oauth_consumer_key": consumer_key,
"oauth_signature_method": "HMAC-SHA1",
"oauth_timestamp": str(int(time.time())),
"oauth_nonce": str(random.getrandbits(64)),
"oauth_version": "1.0"
}
2.APIのパラメーターを加える
signature = dict(baseparam)
signature.update(param)
3.合わさったパラメータを文字列に変える
全てのキーと値はパーセントエンコードする。
キーをABC順に並べる
a=nurupo&b=ga みたいに連結する
signature = '&'.join('{0}={1}'.format(quote(key, ''), quote(signature[key], '~')) for key in sorted(signature))
4.ハッシュ関数とbase64で変換した署名を作成する
method&url&さっきの連結した文字列
というメッセージを
consumer_secret&access_token_secretで署名する
signature = ("{0}&{1}".format(consumer_secret, access_token_secret), '{0}&{1}&{2}'.format(method.upper(), quote(url, ''), quote(signature, '')))
signature = base64.b64encode(hmac.new(signature[0], signature[1], hashlib.sha1).digest())
5. 1.のパラメータと署名を合わせてヘッダに追加
header = dict(baseparam)
header.update({"oauth_signature": signature})
header = ",".join("{0}={1}".format(quote(k, ''), quote(header[k], '~')) for k in sorted(header))
header = {"Authorization": 'OAuth {0}'.format(header)}
6. 送信
#リクエストを送信する関数
# method: "POST" or "GET"
# url: e.g. "https://www.w3.org/History/19921103-hypertext/hypertext/WWW/TheProject.html"
# param: e.g. {"status":"にゃーん"}
# header: e.g. {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64)"}
#
# return: {"code":200,"body":"something"}
def request(method, url, param, header):
method = method.upper()
param = urlencode(param)
if method == "POST":
pass
if method == "GET":
url += "?" + param
param = None
try:
req = urllib2.Request(url, param, header)
handler = urllib2.urlopen(req)
return {"body": handler.read(), "code": handler.getcode()}
except urllib2.HTTPError as e:
print(e.fp.read())
request(method, url, param, header)
APIのサーバー側ではmethod,URL,Authorization,paramから同様にハッシュを計算し一致を確かめるのだろう