質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.58%

JavaからのZAIM APIの呼び出し(oauth)でリクエストトークンが取得できない

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 2,511

score 6

 前提・実現したいこと

Javaで家計簿サイトZAIMのAPIをコールして、家計簿情報を取得・登録するアプリを開発したいとトライしているのですが、
リクエストトークンを取得する際にエラーになっている(?)と思われるところで詰まっています。
初歩的な問題かもしれませんが、助言宜しくお願い致します。

 発生している問題・エラーメッセージ

Exception in thread "main" java.io.IOException: Server returned HTTP response code: 400 for URL: https://api.zaim.net/v2/auth/request
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1838)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1439)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
    at myapp.main(TestZaim.java:87)

 該当のソースコード

//ネット上で有識者が公開して下さっていたソースコードを流用しております。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Encoder;

public class TestZaim {

    public static void main(String[] args) throws Exception {
        // OAuthにおいて利用する変数宣言
        String consumerkey = "略"; 
        String consumerSecret = "略";
        String oauthToken = ""; // リクエストトークン取得時は利用しない
        String oauthTokenSecret = ""; // リクエストトークン取得時は利用しない
        String method = "POST";
        String urlStr = "https://api.zaim.net/v2/auth/request";

        // OAuthにおいて利用する共通パラメーター
        // パラメーターはソートする必要があるためSortedMapを利用
        SortedMap<String, String> params = new TreeMap<String, String>();
        params.put("oauth_consumer_key", consumerkey);
        params.put("oauth_signature_method", "HMAC-SHA1");
        params.put("oauth_timestamp", String.valueOf(getUnixTime()));
        params.put("oauth_nonce", String.valueOf(Math.random()));
        params.put("oauth_version", "1.0");
        // params.put("oauth_token", oauthToken); // リクエストトークン取得時は利用しない

        {
            /*
             * 署名(oauth_signature)の生成
             */
            // パラメーターを連結する
            String paramStr = "";
            for (Entry<String, String> param : params.entrySet()) {
                paramStr += "&" + param.getKey() + "=" + param.getValue();
            }
            paramStr = paramStr.substring(1);

            // 署名対象テキスト(signature base string)の作成
            String text = method + "&" + urlEncode(urlStr) + "&"
                    + urlEncode(paramStr);

            // 署名キーの作成
            String key = urlEncode(consumerSecret) + "&"
                    + urlEncode(oauthTokenSecret);

            // HMAC-SHA1で署名を生成
            SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(),
                    "HmacSHA1");
            Mac mac = Mac.getInstance(signingKey.getAlgorithm());
            mac.init(signingKey);
            byte[] rawHmac = mac.doFinal(text.getBytes());
            String signature = new BASE64Encoder().encode(rawHmac);

            // 署名をパラメータに追加
            params.put("oauth_signature", signature);
        }

        // Authorizationヘッダの作成
        String paramStr = "";
        for (Entry<String, String> param : params.entrySet()) {
            paramStr += ", " + param.getKey() + "=\""
                    + urlEncode(param.getValue()) + "\"";
        }
        paramStr = paramStr.substring(2);
        String authorizationHeader = "OAuth " + paramStr;

        // APIにアクセス
        URL url = new URL(urlStr);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod(method);
        connection.setRequestProperty("Authorization", authorizationHeader);
        connection.connect();
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                connection.getInputStream()));
        String response;
        while ((response = reader.readLine()) != null) {
            System.out.println(response);
        }
    }

    private static int getUnixTime() {
        return (int) (System.currentTimeMillis() / 1000L);
    }

    private static String urlEncode(String string) {
        try {
            return URLEncoder.encode(string, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

 試したこと

ZAIMへアプリ申請し、consumerKey,consumerSecretを取得。
リクエストトークン取得URL:https://api.zaim.net/v2/auth/request

ローカル環境よりeclispe上でJunitテストを実施し、上記エラーが発生。

 補足情報(FW/ツールのバージョンなど)

Java1.6 + Junit
eclipse4.4

★20180311追記
ありがとうございます。Hurl.itにてPOST/oauth1.0aで試行してみましたので、その結果を記述します。

POST https://api.zaim.net/v2/auth/request?oauth_consumer_key=略&oauth_signature=EWWa2vRVJPnAKqXpbS5ZG7l5a0Y=&oauth_timestamp=1520742972&oauth_nonce=14367107062858923431520742972&oauth_version=1.0&oauth_signature_method=HMAC-SHA1

HEADERS
Connection: keep-alive
Content-Type: application/json; charset=utf-8
Date: Sun, 11 Mar 2018 04:36:13 GMT
Server: nginx
Transfer-Encoding: chunked
X-Powered-By: PHP/7.1.13-1+ubuntu16.04.1+deb.sury.org+1

BODY view raw
{
"error": true,
"message": "400 OAuth parameter(s) does not exist: oauth_callback"
}

TestZaim.java 87行目 connectionを実行する箇所です。
BufferedReader reader = new BufferedReader(new InputStreamReader(
connection.getInputStream()));

★20180311追記

・呼び出し方事例(公式 https://oauth.net/core/1.0a/#anchor43)
After Jane informs printer.example.com that she would like to print her vacation photo stored at photos.example.net, the printer website tries to access the photo and receives HTTP 401 Unauthorized indicating it is private. The Service Provider includes the following header with the response:

WWW-Authenticate: OAuth realm="http://photos.example.net/"
The Consumer sends the following HTTP POST request to the Service Provider:

https://photos.example.net/request_token?oauth_consumer_key=dpf43f3p2l4k3l03&oauth_signature_method=PLAINTEXT&oauth_signature=kd94hf93k423kf44%26&oauth_timestamp=1191242090&oauth_nonce=hsu94j3884jdopsl&oauth_version=1.0&oauth_callback=http%3A%2F%2Fprinter.example.com%2Frequest_token_ready
The Service Provider checks the signature and replies with an unauthorized Request Token in the body of the HTTP response:

oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03&oauth_callback_confirmed=true

・phpコードサンプル(公式 https://dev.zaim.net/home/api/authorize)
<?php
require_once('HTTP/OAuth/Consumer.php');
session_start();

// Provider info
$provider_base = 'https://api.zaim.net/v2/auth/';
$request_url = $provider_base.'request';
$authorize_url = 'https://auth.zaim.net/users/auth';
$access_url = $provider_base.'access';
$resource_url = 'https://api.zaim.net/v2/home/user/verify';

// Consumer info
$consumer_key = YOUR_CONSUMER_KEY;
$consumer_secret = YOUR_CONSUMER_SECRET;
$callback_url = sprintf('http://%s%s', $_SERVER['HTTP_HOST'], $_SERVER['SCRIPT_NAME']);

// Session clear
if (isset($_REQUEST['action']) &&
$_REQUEST['action'] === 'clear') {
session_destroy();
$_SESSION = array();
session_start();
}

$content = '';
try {
// Initialize HTTP_OAuth_Consumer
$oauth = new HTTP_OAuth_Consumer($consumer_key, $consumer_secret);

// Enable SSL
$http_request = new HTTP_Request2();
$http_request->setConfig('ssl_verify_peer', false);
$consumer_request = new HTTP_OAuth_Consumer_Request;
$consumer_request->accept($http_request);
$oauth->accept($consumer_request);

if (!isset($_SESSION['type'])) $_SESSION['type'] = null;

// 2 Authorize
if ($_SESSION['type']=='authorize' &&
isset($_GET['oauth_token'], $_GET['oauth_verifier'])) {
// Exchange the Request Token for an Access Token
$oauth->setToken($_SESSION['oauth_token']);
$oauth->setTokenSecret($_SESSION['oauth_token_secret']);
$oauth->getAccessToken($access_url, $_GET['oauth_verifier']);

// Save an Access Token
$_SESSION['type'] = 'access';
$_SESSION['oauth_token'] = $oauth->getToken();
$_SESSION['oauth_token_secret'] = $oauth->getTokenSecret();
}

// 3 Access
if ($_SESSION['type']=='access') {
// Accessing Protected Resources
$oauth->setToken($_SESSION['oauth_token']);
$oauth->setTokenSecret($_SESSION['oauth_token_secret']);
$result = $oauth->sendRequest($resource_url, array(), 'GET');

$content = $result->getBody();

// 1 Request
} else {
// Get a Request Token
$oauth->getRequestToken($request_url, $callback_url);

// Save a Request Token
$_SESSION['type'] = 'authorize';
$_SESSION['oauth_token'] = $oauth->getToken();
$_SESSION['oauth_token_secret'] = $oauth->getTokenSecret();

// Get an Authorize URL
$authorize_url = $oauth->getAuthorizeURL($authorize_url);

$content = "Click the link.<br />\n";
$content .= sprintf('<a href="%s">%s</a>', $authorize_url, $authorize_url);
}

} catch (Exception $e) {
$content .= $e->getMessage();
}
?>
<html>
<head>
<title>OAuth in PHP</title>
</head>
<body>
<h2>Welcome to a Zaim OAuth PHP example.</h2>
<p><a href='?action=clear'>Clear sessions</a></p>
<p><pre><?php print_r($content); ?><pre></p>
</body>
</html>

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • tamechop

    2018/03/12 23:59

    認証用URLを入れると、ZAIM側でリクエストトークンを保持したまま認証用URLへリダイレクトしてくれる仕組みのように読めました。一歩進むことができましたので、もう一度API文献を理解しようと思います。rarara様、相談に乗って頂き、誠にありがとうございました。

    キャンセル

  • rarara

    2018/03/13 00:33

    おーよかったです。自己解決ってことで、時間がある時に回答部分に投稿しておいていただけると、teratail的に良いと思いますのでよろしくお願いします。

    キャンセル

  • tamechop

    2018/03/14 20:57

    了解致しました。java上でのコールを試行した上で記述しておきたいと思います。大変お世話になりました。

    キャンセル

回答 1

check解決した方法

0

【課題】 javaでRequestTokenを取得できない。
【対応】 oauth_callbackが設定されていなかったため、ボディへ追加し、
RequestToken取得URL(API)をコールした。
https://www.hurl.it/ ※上記要因であることを左記ページ様にて確認
【結果】 oauth_token, oauth_token_secret, oauth_callback_confirmedを取得できた。
oauth_token=xxxx&oauth_token_secret=xxxx&oauth_callback_confirmed=true
【成果物】
最終的には以下内容で、JavaからAccessToken認証URLを開くまで、確認できました。
★scribejavaを利用

--Java
OAuthConsumer mConsumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
OAuthProvider mProvider = new CommonsHttpOAuthProvider(REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZE_URL);
String authUrl = mProvider.retrieveRequestToken(mConsumer, CALLBACK);
// ブラウザに認証ページを開かせる
Desktop desktop = Desktop.getDesktop();
String uriString = authUrl;
try {
URI uri = new URI(uriString);
desktop.browse(uri);
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

--POM
<dependency>
<groupId>com.github.scribejava</groupId>
<artifactId>scribejava-apis</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>com.github.scribejava</groupId>
<artifactId>scribejava-core</artifactId>
<version>5.3.0</version>
</dependency>

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.58%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る