このページの内容
アクセストークンの取得
ツイートの検索(SearchAPI)や、ツイートの埋め込み(oEmbedAPI)を行なう場合は、ベアラートークン(bearer token)を使って認証できましたが、ツイートを発行する場合には、アカウントと紐づいている必要があるので、この方法は使えません。自分のアカウントからAPIへアクセスしたい場合には、dev.twitter.comから取得するトークンを使って認証を行います。
下記の4つの項目をメモしておいてください。
OAuth 1.0a HMAC-SHA1 署名の生成
パラメータの作成
キー | 値 |
---|---|
status | ツイートに含める文字列 |
oauth_consumer_key | 上記で取得した CONSUMER_KEY |
oauth_nonce | リクエストごとに生成する重複のない文字列 |
oauth_signature_method | HMAC-SHA1 |
oauth_timestamp | リクエストが生成されたタイミングのUnixエポック秒 |
oauth_token | 上記で取得した ACCESS_TOKEN |
oauth_version | 1.0 |
- 上記のキーと値をそれぞれURLエンコードする。
- キーと値を "=" で連結する。
- すべてのパラメータを "&" で連結する。(キーのアルファベット順で連結する必要があります。)
そうすると、下記のような文字列が出来上がります。
oauth_consumer_key=xvz1evFS4wEEPTGEFPHBog&oauth_nonce=kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1318622958&oauth_token=370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb&oauth_version=1.0&status=Hello%20Ladies%20%2B%20Gentlemen%2C%20a%20signed%20OAuth%20request%21
署名キーの生成
上記で取得した CONSUMER_SECRET と ACCESS_TOKEN_SECRET をURLエンコードして、"&" で連結したものです。
そうすると、下記のような文字列が出来上がります。
kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE
署名データの生成
下記の順で文字列を組み立てます。
- "POST" (HTTPメソッドを大文字に変換したもの)
- "&"
- "https://api.twitter.com/1.1/statuses/update.json" をURLエンコードしたもの
- "&"
- 作成したパラメータ文字列
そうすると下記のような文字列が出来上がります。
POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521
署名の作成
java で HMAC-SHA1 形式の署名を作成する場合は下記のようにします。
引数に署名のキーと署名のデータを渡すと、署名文字列を返します。
import java.util.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; public String getSignature(String key, String data) { SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1"); Mac mac = Mac.getInstance(signingKey.getAlgorithm()); mac.init(signingKey); byte[] rawHmac = mac.doFinal(data.getBytes()); String signature = Base64.getEncoder().encodeToString(rawHmac); return signature; }
Authorizationヘッダの作成
キー | 値 |
---|---|
oauth_consumer_key | 先に取得しておいた CONSUMER_KEY |
oauth_nonce | リクエストごとに生成する重複のない文字列 |
oauth_signature | 上記で作成した署名文字列 |
oauth_signature_method | HMAC-SHA1 |
oauth_timestamp | リクエストが生成されたタイミングのUnixエポック秒 |
oauth_token | 先に取得しておいた ACCESS_TOKEN |
oauth_version | 1.0 |
- 上記のキーと値をそれぞれURLエンコードする。
- 値を "(ダブルクォーテーション)で囲む。
- 1. と 2. を "=" で連結する。
- すべてのパラメータを ", " で連結する。(カンマと半角スペース)
- 先頭に "OAuth " を付ける。(半角スペースを忘れずに)
そうすると下記のような文字列が出来上がります。これをリクエストのAuthorizationヘッダに設定します。
OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog", oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg", oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1318622958", oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb", oauth_version="1.0"
実装例
Java
下記のライブラリを使用しています。
- OkHttp3
下記のハイライト部分はプロパティファイル等に定義するのが良いと思います。
- CONSUMER_KEY
- CONSUMER_SECRET
- OAUTH_TOKEN
- OAUTH_TOKEN_SECRET
import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.util.Base64; import java.util.Iterator; import java.util.Map.Entry; import java.util.SortedMap; import java.util.TreeMap; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class TwitterBotService { public static final String CONSUMER_KEY = "your consumer key here."; public static final String CONSUMER_SECRET = "your consumer secret here."; public static final String OAUTH_TOKEN = "your oauth token here."; public static final String OAUTH_TOKEN_SECRET = "your oauth token secret here."; public static void main(String[] args) throws Exception { TwitterBotService app = new TwitterBotService(); app.tweet("ツイートサンプル #test"); } /** * OAuth1.0用パラメータの作成(oauth_signatureは除く) * @return OAuth1.0用パラメータ */ private SortedMap<String, String> createOauthParamsExceptSignature() { // キーのアルファベット順である必要があるので、SortedMapを使う SortedMap<String, String> params = new TreeMap<>(); params.put("oauth_consumer_key", CONSUMER_KEY); params.put("oauth_signature_method", "HMAC-SHA1"); params.put("oauth_timestamp", String.valueOf(Instant.now().getEpochSecond())); params.put("oauth_nonce", String.valueOf(Math.random())); params.put("oauth_version", "1.0"); params.put("oauth_token", OAUTH_TOKEN); return params; } /** * パラメータ文字列の作成 * @param oauthParams OAuth1.0用パラメータ * @param status ツイート文言 * @return パラメータ文字列 */ private String createParamsString(SortedMap<String, String> oauthParams, String status) { // パラメータをコピー SortedMap<String, String> params = new TreeMap<>(oauthParams); // status を追加 params.put("status", urlEncode(status)); Iterator<Entry<String, String>> ite = params.entrySet().iterator(); StringBuilder sb = new StringBuilder(); while (ite.hasNext()) { Entry<String, String> entry = ite.next(); sb.append("&"); sb.append(entry.getKey()); sb.append("="); sb.append(entry.getValue()); } return sb.substring(1); } /** * 署名キーの作成 * @return 署名キー */ private String createSignatureKey() { return urlEncode(CONSUMER_SECRET) + "&" + urlEncode(OAUTH_TOKEN_SECRET); } /** * 署名データの作成 * @param method HTTPメソッド(大文字) * @param urlStr URL * @param paramStr パラメータ文字列 * @return 署名データ */ private String createSignatureData(String method, String urlStr, String paramStr) { return method + "&" + urlEncode(urlStr) + "&" + urlEncode(paramStr); } /** * 署名の算出 * @param key 署名キー * @param data 署名データ * @return 署名文字列 */ private String calcSignature(String key, String data) { try { SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1"); Mac mac = Mac.getInstance(signingKey.getAlgorithm()); mac.init(signingKey); byte[] rawHmac = mac.doFinal(data.getBytes()); System.out.println(rawHmac); return Base64.getEncoder().encodeToString(rawHmac); } catch (NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException(e); } } /** * Authorizationヘッダ文字列の作成 * @param oauthParams OAuth1.0用パラメータ * @return Authorizationヘッダ文字列 */ private String createAuthorizationHeader(SortedMap<String, String> oauthParams) { Iterator<Entry<String, String>> ite = oauthParams.entrySet().iterator(); StringBuilder sb = new StringBuilder(); while (ite.hasNext()) { Entry<String, String> entry = ite.next(); sb.append(", "); sb.append(entry.getKey()); sb.append("=\"").append(urlEncode(entry.getValue())).append("\""); } return "OAuth " + sb.substring(2); } /** * URLエンコード * @param str 対象文字列 * @return URLエンコードした文字列 */ private static String urlEncode(String str) { try { return URLEncoder.encode(str, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } /** * ツイート発行 * @param status ツイート文言 * @throws Exception リクエスト失敗 */ public void tweet(String status) throws Exception { String method = "POST"; String urlStr = "https://api.twitter.com/1.1/statuses/update.json"; // 署名(oauth_signature)以外の oauth_* パラメータを作成 SortedMap<String, String> oauthParams = createOauthParamsExceptSignature(); // 署名を算出 String key = createSignatureKey(); String data = createSignatureData(method, urlStr, createParamsString(oauthParams, status)); String signature = calcSignature(key, data); // 署名を oauth_* パラメータに追加 oauthParams.put("oauth_signature", signature); // Authorizationヘッダを作成 String authorizationHeader = createAuthorizationHeader(oauthParams); // HTTPリクエストbody に status (ツイート文言) を設定 RequestBody body = RequestBody.create( MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"), "status=" + urlEncode(status)); // ツイート文言 // リクエスト発行 Request request = new Request.Builder() .url(urlStr) .addHeader("Authorization", authorizationHeader) .post(body) .build(); OkHttpClient client = new OkHttpClient(); Response response = client.newCall(request).execute(); String responseStr = response.body().string(); System.out.println(responseStr); } }
エラーコードが返ってきた場合
同じ文言を短時間のうちに2度ツイートすると、下記のようなエラーでブロックされる仕様です。文言を変えてうまくいくかどうかを試しましょう。
{"errors":[{"code":32,"message":"Could not authenticate you."}]}
参考
- https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update.html
- https://developer.twitter.com/en/docs/basics/authentication/guides/creating-a-signature.html
- https://developer.twitter.com/en/docs/basics/authentication/guides/authorizing-a-request.html
- https://developer.twitter.com/en/docs/basics/response-codes.html