C++でのTwitterクライアントプログラムのOAuth認証
C++を利用してTwitterクライアントプログラムを開発したいです。
外部ライブラリはOpenSSL(暗号化)とcpprestsdk(HTTP通信)のみです。
OAuth認証も自分で実装しようと思うのですが、うまくいきません。
具体的にはシグネチャ?の作成とPOSTするデータ(パラメータ)の作成がうまく行っていないのかも...と思っています。
Twicppsとこのサイト様を参考にさせていただいているのですが、サイトだとHTTPリクエストヘッダにAuthorizationというヘッダを作り、そこにもOAuth認証情報を記載するとの事が書いてあるのですが、Twicppsのコードを見ているとそのようなことをしているようには見えません。
パラメータの作成は行っていますが、urlとパラメータだけを乗っけてPOSTしているようなコードです。
以下のコード(.hpp)で通信はできるのですが、スターテスコード400で帰ってきて、レスポンスは以下のとおりです。 (訂正:ツイートはできましたが、Authorizationヘッダが必要な通信が実装できません)
{"errors":[{"code":215,"message":"Bad Authentication data."}]}
基本はTwicpps寄りで開発してきたのですが、Authorizationをつけないと認証はできないようです。
以下のコード全文を載せます。cppの方はmain関数からtweet関数を呼び出しているだけですので掲載しません。
どこがおかしいのか指摘して頂けると光栄です。
よろしくおねがいします。
OS:Windows10 Home
IDE:Visual Studio 2019 Community
OpenSSL:OpenSSL-Win32 1.0.2r
cpprestsdk:cpprestsdk v141
[Header.hpp]
#pragma once
#include <sstream>
#include <fstream>
#include <vector>
#include <locale>
#include <winstring.h>
#include <algorithm>
#include <iomanip>
#include <queue>
#include <time.h>
#include <cpprest/http_client.h>
#include <openssl/hmac.h>
#include <openssl/ssl.h>
#pragma comment(lib,"libeay32.lib")
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
#define REST_POST_PATH "C:\\ProgramData\\System72\\CAIOS\\CAIOS\\REST_POST_RESPONSE.json"
#define REST_GET_PATH "C:\\ProgramData\\System72\\CAIOS\\CAIOS\\REST_GET_RESPONSE.json"
#define TWITTER_URL "https://api.twitter.com/1.1/statuses/update.json"
typedef enum {
POST,
GET
} METHOD;
enum CodePageID : unsigned int {
ANSI = CP_ACP, // ANSI
OEM = CP_OEMCP, // OEM(依存)
MAC = CP_MACCP, // MAC
UTF7 = CP_UTF7, // UTF-7
UTF8 = CP_UTF8 // UTF-8
};
struct Request {
string url;
string post;
};
struct TwitterAPI_Keys {
string Consumer_Key = "ここにキーを代入";
string Consumer_Sec = "ここにキーを代入";
string Accesstoken = "ここにキーを代入";
string Accesstoken_Aec = "ここにキーを代入";
};
namespace CAIOS {
namespace String {
static string UTF8_to_SJIS(string message) {
int n;
wchar_t ucs2[1000];
char utf8[1000];
n = MultiByteToWideChar(CP_UTF8, 0, message.c_str(), message.size(), ucs2, 1000);
n = WideCharToMultiByte(CP_ACP, 0, ucs2, n, utf8, 1000, 0, 0);
return std::string(utf8, n);
}
static string SJIS_to_UTF8(std::string const& message) {
int n;
wchar_t ucs2[1000];
char utf8[1000];
n = MultiByteToWideChar(CP_ACP, 0, message.c_str(), message.size(), ucs2, 1000);
n = WideCharToMultiByte(CP_UTF8, 0, ucs2, n, utf8, 1000, 0, 0);
return std::string(utf8, n);
}
// string から wstring 変換
static wstring StringToWString(const string& refSrc, unsigned int codePage = CodePageID::ANSI) {
vector<wchar_t> buffer(MultiByteToWideChar(codePage, 0, refSrc.c_str(), -1, nullptr, 0));
MultiByteToWideChar(codePage, 0, refSrc.c_str(), -1, &buffer.front(), buffer.size());
return wstring(buffer.begin(), buffer.end());
}
// wstring から string 変換
static string WStringToString(const wstring& refSrc, unsigned int codePage = CodePageID::OEM) {
vector<char> buffer(WideCharToMultiByte(codePage, 0, refSrc.c_str(), -1, nullptr, 0, nullptr, nullptr));
WideCharToMultiByte(codePage, 0, refSrc.c_str(), -1, &buffer.front(), buffer.size(), nullptr, nullptr);
return string(buffer.begin(), buffer.end());
}
static string EraseString(string str, string erase) {
for (size_t c = str.find_first_of(erase); c != string::npos; c = c = str.find_first_of(erase)) {
str.erase(c, 1);
}
return str;
}
}
namespace REST {
static string GET(Request req) {
try {
using namespace CAIOS::String;
http_client client(StringToWString(req.url));
cout << " -> HTTP request mode [POST]" << endl;
cout << " -> HTTP request to " << req.url << endl;
auto response = client.request(methods::GET).get();
auto str = response.extract_string();
cout << " -> Server returned returned status code " << response.status_code() << '.' << endl;
cout << " -> Content length is " << response.headers().content_length() << " bytes.\n" << endl;
ofstream ofs(REST_POST_PATH);
ofs << WStringToString(str.get().c_str()) << endl;
ofs.close();
return WStringToString(str.get().c_str());
}
catch (...) {
cout << "HTTP通信中に例外が発生しました [GET]" << endl;
}
}
static string POST(Request req) {
try {
using namespace CAIOS::String;
http_client client(StringToWString(req.url));
http_request request(methods::POST);
cout << " -> HTTP request mode [POST]" << endl;
cout << " -> HTTP request to " << req.url << endl;
char body[3000];
sprintf_s(body, 3000, "Authorization: OAuth %s", req.post);
request.headers().add(L"Content-Type", L"application/x-www-form-urlencoded");
request.set_body((wchar_t*)body);
auto response = client.request(request).get();
auto str = response.extract_string();
cout << " -> Server returned returned status code " << response.status_code() << '.' << endl;
cout << " -> Content length is " << response.headers().content_length() << " bytes.\n" << endl;
ofstream ofs(REST_POST_PATH);
ofs << WStringToString(str.get().c_str()) << endl;
ofs.close();
return WStringToString(str.get().c_str());
}
catch (...) {
cout << "HTTP通信中に例外が発生しました [POST]" << endl;
}
}
static string URL_encode(string str) {
const int NUM_BEGIN_UTF8 = 48;
const int CAPITAL_BEGIN_UTF8 = 65;
const int LOWER_BEGIN_UTF8 = 97;
int charCode = -1;
string encoded;
stringstream out;
for (int i = 0; str[i] != 0; i++) {
charCode = (int)(unsigned char)str[i];
//エンコードする必要の無い文字の判定
if ((NUM_BEGIN_UTF8 <= charCode && charCode <= NUM_BEGIN_UTF8 + 9)
|| (CAPITAL_BEGIN_UTF8 <= charCode && charCode <= CAPITAL_BEGIN_UTF8 + 25)
|| (LOWER_BEGIN_UTF8 <= charCode && charCode <= LOWER_BEGIN_UTF8 + 25)
|| str[i] == '.' || str[i] == '_' || str[i] == '-' || str[i] == '~'){
out << str[i];
}
else {
out << '%' << hex << uppercase << charCode;
}
}
encoded = out.str();
return encoded;
}
}
namespace Twitter {
namespace OAuth {
static string CreateData(vector<string>const OAuth, METHOD method, int Start) {
string query;
for (int t = Start; t < OAuth.size(); t++) {
if (t != Start)query += "&";
query += OAuth[t];
}
return query;
}
static int split_url(const string url, vector<string>* OAuth) {
int num = url.find_first_of('?');
OAuth->push_back(url.substr(0, num));
OAuth->push_back(url.substr(num + 1));
return 0;
}
static string sha1(const string Key, const string Data) {
char* key = (char*)Key.c_str();
char* data = (char*)Data.c_str();
unsigned char res[SHA_DIGEST_LENGTH + 1];
size_t reslen;
HMAC(EVP_sha1(), key, strlen(key), reinterpret_cast<const unsigned char*>(data), strlen(data), res, &reslen);
return string(reinterpret_cast<char*>(res), reslen);
}
static int encode_base64(char* bufin, int len, char* bufout){
static unsigned char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
unsigned char* pin = (unsigned char*)bufin;
unsigned char* pout = (unsigned char*)bufout;
for (int i = 0; i < len - 2; i += 3){
*pout++ = base64[pin[0] >> 2];
*pout++ = base64[0x3F & ((pin[0] << 4) | (pin[1] >> 4))];
*pout++ = base64[0x3F & ((pin[1] << 2) | (pin[2] >> 6))];
*pout++ = base64[0x3F & pin[2]];
pin += 3;
}
if (len % 3 == 1){
*pout++ = base64[pin[0] >> 2];
*pout++ = base64[0x3F & (pin[0] << 4)];
*pout++ = '=';
*pout++ = '=';
}
else if (len % 3 == 2){
*pout++ = base64[pin[0] >> 2];
*pout++ = base64[0x3F & ((pin[0] << 4) | (pin[1] >> 4))];
*pout++ = base64[0x3F & (pin[1] << 2)];
*pout++ = '=';
}
*pout = '\0';
return pout - (unsigned char*)bufout;
}
static string CreateSignature(string ConsumerSecret, string AccessSecret, vector<string>const& OAuth, METHOD method, int Start = 1) {
string str, key, data, methods;
char out[256];
if (method == POST)methods = "POST";
else methods = "GET";
key = ConsumerSecret + "&" + AccessSecret;
data = methods + "&" + CAIOS::REST::URL_encode(OAuth[0]) + "&" + CreateData(OAuth, method, Start);
str = sha1(key, data);
CAIOS::Twitter::OAuth::encode_base64((char*)str.c_str(), sizeof(str) - 1, out);
return out;
}
static int IntOAuthParams(vector<string>* OAuth, METHOD method) {
TwitterAPI_Keys key;
auto CreateNonce = []() {
static const char* chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
const unsigned int max = 26 + 26 + 10 + 1;
char tmp[50];
srand((unsigned int)time(0));
int len = 15 + rand() % 16;
for (int i = 0; i < len; i++) {
tmp[i] = chars[rand() % max];
}
return std::string(tmp, len);
};
string oauth_nonce = "oauth_nonce";
oauth_nonce += "=";
oauth_nonce += CreateNonce();
OAuth->push_back(oauth_nonce);
string oauth_timestamp = "oauth_timestamp";
oauth_timestamp += "=";
oauth_timestamp += to_string((int)time(nullptr));
OAuth->push_back(oauth_timestamp);
string oauth_token = "oauth_token";
oauth_token += "=";
oauth_token += key.Accesstoken;
OAuth->push_back(oauth_token);
string oauth_consumer_key = "oauth_consumer_key";
oauth_consumer_key += "=";
oauth_consumer_key += key.Consumer_Key;
OAuth->push_back(oauth_consumer_key);
string oauth_signature_method = "oauth_signature_method";
oauth_signature_method += "=";
oauth_signature_method += "HMAC-SHA1";
OAuth->push_back(oauth_signature_method);
string oauth_version = "oauth_version";
oauth_version += "=";
oauth_version += "1.0";
OAuth->push_back(oauth_version);
sort(OAuth->begin() + 1, OAuth->end());
string oauth_signature = "oauth_signature";
oauth_signature += "=";
oauth_signature += CreateSignature(key.Consumer_Sec, key.Accesstoken_Aec, *OAuth, method);
OAuth->push_back(oauth_signature);
return 0;
}
static Request OAuthAuthentication(string url, METHOD method) {
vector<string> OAuth;
split_url(url, &OAuth);
CAIOS::Twitter::OAuth::IntOAuthParams(&OAuth, method);
Request req;
if (method == POST) {
req.url = OAuth[0];
req.post = CAIOS::Twitter::OAuth::CreateData(OAuth, method, 1);
}
else {
req.url = OAuth[0];
req.post = CAIOS::Twitter::OAuth::CreateData(OAuth, method, 1);
}
return req;
}
}
static string tweet(string message) {
message = CAIOS::String::SJIS_to_UTF8(message);
string url = TWITTER_URL;
url += "?status=" + CAIOS::REST::URL_encode(message);
Request req = OAuth::OAuthAuthentication(url, POST);
return CAIOS::REST::POST(req);
}
}
}
補足
Authorizationヘッダが必要な場合、どのようにヘッダをつければいいのでしょうか。TwitterではAuthorizationヘッダが必要でない場合と必要な場合?の2つがあるようですが、このときは必要。このときは不要などのラインを教えてくださるとありがたいです。