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

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

ただいまの
回答率

90.53%

  • Twitter

    775questions

    Twitterは、140文字以内の「ツイート」と呼ばれる短文を投稿できるサービスです。Twitter上のほぼ全ての機能に対応するAPIが存在し、その関連サービスが多く公開されています。

  • Scala

    209questions

    ScalaはJava仮想マシンで動作を行うオブジェクト指向型プログラミング言語の1つです。静的型付けの関数型言語で、コンパイルエラーの検出に強みがあります。

  • OAuth

    123questions

    OAuth(Open Authorization)は、APIを通して保護されたリソース(サードパーティのアプリケーション)へアクセスする為のオープンプロトコルです。

ScalaでTwitterのOAuth1.0認証ができない

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 709

Zomathi

score 5

概要

Scalaで, GET https://api.twitter.com/1.1/statuses/user_timeline.json?count=1&user_id=XXXというリクエストをするために,以下のコードを書いたところ,401 Authorization requiredというレスポンスが返ってきた.

環境

  • macOS Sierra 10.12.2
  • Scala 2.12.1
  • sbt 0.13.13

build.sbt:

name := "hoge"

version := "1.0"

libraryDependencies ++= Seq(
  "com.typesafe.play" %% "play-ws" % "2.4.0-M2",
  "commons-codec" % "commons-codec" % "1.3.0"
)

状況

hoge.scalaに次のコードを書きました:

import scala.concurrent.ExecutionContext.Implicits.global
import java.net.URLEncoder
import javax.crypto.spec.SecretKeySpec
import javax.crypto.Mac
import play.api.libs.json._
import play.api.libs.ws.ning.NingWSClient
import org.apache.commons.codec.binary.Base64

object Hoge {
  def main(args: Array[String]) = {
    val consumerKey = "AAA"
    val consumerSecret = "BBB"
    val token = "CCC"
    val tokenSecret = "DDD"
    val timestamp = (System.currentTimeMillis / 1000).toString
    val nonce = System.nanoTime.toString
    val id = "XXX"

    val host = "https://api.twitter.com"
    val path = "/1.1/statuses/user_timeline.json"
    val url = host + path

    val query = Map(
      "count" -> "1",
      "user_id" -> id
    )

    val sign = signature(
      url, consumerKey, consumerSecret, token, tokenSecret, timestamp, nonce, query
    )
    val header = Map(
      "Content-Type" -> "application/x-www-form-urlencoded;charset=UTF-8",
      "Authorization" -> oauthHeader(consumerKey, token, sign, timestamp, nonce)
    )

    val ws = NingWSClient()
    val res = get(ws)(url, query, header)
    Thread.sleep(1000)
    res.value.get.get.json
    ws.close()
  }

  def signature(url: String, consumerKey: String, consumerSecret: String, token: String, tokenSecret: String,
                        timestamp: String, nonce: String, query: Map[String, String]) = {
    val params = Map(
      "oauth_consumer_key" -> consumerKey,
      "oauth_nonce" -> nonce,
      "oauth_signature_method" -> "HMAC-SHA1",
      "oauth_timestamp" -> timestamp,
      "oauth_token" -> token,
      "oauth_version" -> "1.0"
    ) ++ query
    val paramsEncoded = params.toList.sorted map { case (k, v) => urlencode(k) + "=" + urlencode(v) }
    val paramString = paramsEncoded mkString "&"
    val signatureBase = "GET&" + urlencode(url) + "&" + urlencode(paramString)
    val signingKey = urlencode(consumerSecret) + "&" + urlencode(tokenSecret)
    base64encode(encry(signingKey, signatureBase))
  }

  def oauthHeader(consumerKey: String, token: String, sign: String, timestamp: String, nonce: String) = {
    val params = Map(
      "oauth_consumer_key" -> consumerKey,
      "oauth_nonce" -> nonce,
      "oauth_signature" -> sign,
      "oauth_signature_method" -> "HMAC-SHA1",
      "oauth_timestamp" -> timestamp,
      "oauth_token" -> token,
      "oauth_version" -> "1.0"
    ).toList.sorted
    "OAuth " + (params map { case (k, v) => urlencode(k) + "=\"" + urlencode(v) + "\"" } mkString ", ")
  }

  def urlencode(s: String) = {
    URLEncoder.encode(s, "UTF-8")
  }

  def base64encode(s: String) = {
    Base64.encodeBase64String(s.getBytes("UTF-8"))
  }

  def encry(key: String, data: String) = {
    val secret = new SecretKeySpec(key.getBytes, "HmacSHA1")
    val mac = Mac.getInstance("HmacSHA1")
    mac.init(secret)
    val res = mac.doFinal(data.getBytes("UTF-8"))
    res map { r => "%02x".format(r) } mkString ""
  }

  def get(ws: NingWSClient)(url: String, query: Map[String, String], header: Map[String, String]) = {
    ws.url(url).withQueryString(query.toList: _*).withHeaders(header.toList: _*).get
  }
}

これをsbt compilesbt runで実行したら,上述の401が返ってきました
val ws = NingWSClient以下は問題ないと思います.他の(OAuth1.0認証を用いない)GETやPOSTをした場合は,正常なレスポンスが返ってきました.
consumer keyやtokenなども間違ってはいないです.Rubyで同様の処理をしたら,問題なく実行されました.
署名の作成手順は,公式ドキュメントを何度も読みながら書いたので,間違いないと思います.

timestampnoncesignoauthHeaderはそれぞれ次のようになりました:

timestamp = 1482542041
nonce = 214129951907166
sign = YWQ2Y2VjNzJkOTQwY2JhYjEzYzgxZDM2YTg3ZGNlNjQ3ZjIzMDQ4Yw==
oauthHeader = OAuth oauth_consumer_key="AAA", oauth_nonce="214129951907166", oauth_signature="YWQ2Y2VjNzJkOTQwY2JhYjEzYzgxZDM2YTg3ZGNlNjQ3ZjIzMDQ4Yw%3D%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1482542041", oauth_token="CCC", oauth_version="1.0"

なので,暗号化,エンコード(urlencodebase64encodeencry)のあたりがおかしいのだと思いますが,どうなのでしょう.
Scalaの練習のために作っているので,出来る限りTwitter4Jのようなものは使いたくないです.
どこを修正したらよいのか教えてください.

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

check解決した方法

0

URLEncoder.encodeは空白を'%20'ではなく'+'に変換するため,replace("+", "%20")とする.また,暗号化は

val hmacShai = "HmacSHA1"
    val keyStr = new SecretKeySpec(bytes(key), hmacShai)
    val sig = {
      val mac = Mac.getInstance(hmacShai)
      mac.init(keyStr)
      new String(base64encode(mac.doFinal(bytes(data))))
    }


とする.以上の変更を加えると正常に動いた.

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

  • Twitter

    775questions

    Twitterは、140文字以内の「ツイート」と呼ばれる短文を投稿できるサービスです。Twitter上のほぼ全ての機能に対応するAPIが存在し、その関連サービスが多く公開されています。

  • Scala

    209questions

    ScalaはJava仮想マシンで動作を行うオブジェクト指向型プログラミング言語の1つです。静的型付けの関数型言語で、コンパイルエラーの検出に強みがあります。

  • OAuth

    123questions

    OAuth(Open Authorization)は、APIを通して保護されたリソース(サードパーティのアプリケーション)へアクセスする為のオープンプロトコルです。