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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Scala

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

OAuth

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

Twitter

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

Q&A

解決済

1回答

1982閲覧

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

Zomathi

総合スコア26

Scala

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

OAuth

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

Twitter

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

0グッド

0クリップ

投稿2016/12/24 01:17

#概要
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に次のコードを書きました:

scala:hoge.scala

1import scala.concurrent.ExecutionContext.Implicits.global 2import java.net.URLEncoder 3import javax.crypto.spec.SecretKeySpec 4import javax.crypto.Mac 5import play.api.libs.json._ 6import play.api.libs.ws.ning.NingWSClient 7import org.apache.commons.codec.binary.Base64 8 9object Hoge { 10 def main(args: Array[String]) = { 11 val consumerKey = "AAA" 12 val consumerSecret = "BBB" 13 val token = "CCC" 14 val tokenSecret = "DDD" 15 val timestamp = (System.currentTimeMillis / 1000).toString 16 val nonce = System.nanoTime.toString 17 val id = "XXX" 18 19 val host = "https://api.twitter.com" 20 val path = "/1.1/statuses/user_timeline.json" 21 val url = host + path 22 23 val query = Map( 24 "count" -> "1", 25 "user_id" -> id 26 ) 27 28 val sign = signature( 29 url, consumerKey, consumerSecret, token, tokenSecret, timestamp, nonce, query 30 ) 31 val header = Map( 32 "Content-Type" -> "application/x-www-form-urlencoded;charset=UTF-8", 33 "Authorization" -> oauthHeader(consumerKey, token, sign, timestamp, nonce) 34 ) 35 36 val ws = NingWSClient() 37 val res = get(ws)(url, query, header) 38 Thread.sleep(1000) 39 res.value.get.get.json 40 ws.close() 41 } 42 43 def signature(url: String, consumerKey: String, consumerSecret: String, token: String, tokenSecret: String, 44 timestamp: String, nonce: String, query: Map[String, String]) = { 45 val params = Map( 46 "oauth_consumer_key" -> consumerKey, 47 "oauth_nonce" -> nonce, 48 "oauth_signature_method" -> "HMAC-SHA1", 49 "oauth_timestamp" -> timestamp, 50 "oauth_token" -> token, 51 "oauth_version" -> "1.0" 52 ) ++ query 53 val paramsEncoded = params.toList.sorted map { case (k, v) => urlencode(k) + "=" + urlencode(v) } 54 val paramString = paramsEncoded mkString "&" 55 val signatureBase = "GET&" + urlencode(url) + "&" + urlencode(paramString) 56 val signingKey = urlencode(consumerSecret) + "&" + urlencode(tokenSecret) 57 base64encode(encry(signingKey, signatureBase)) 58 } 59 60 def oauthHeader(consumerKey: String, token: String, sign: String, timestamp: String, nonce: String) = { 61 val params = Map( 62 "oauth_consumer_key" -> consumerKey, 63 "oauth_nonce" -> nonce, 64 "oauth_signature" -> sign, 65 "oauth_signature_method" -> "HMAC-SHA1", 66 "oauth_timestamp" -> timestamp, 67 "oauth_token" -> token, 68 "oauth_version" -> "1.0" 69 ).toList.sorted 70 "OAuth " + (params map { case (k, v) => urlencode(k) + "=\"" + urlencode(v) + "\"" } mkString ", ") 71 } 72 73 def urlencode(s: String) = { 74 URLEncoder.encode(s, "UTF-8") 75 } 76 77 def base64encode(s: String) = { 78 Base64.encodeBase64String(s.getBytes("UTF-8")) 79 } 80 81 def encry(key: String, data: String) = { 82 val secret = new SecretKeySpec(key.getBytes, "HmacSHA1") 83 val mac = Mac.getInstance("HmacSHA1") 84 mac.init(secret) 85 val res = mac.doFinal(data.getBytes("UTF-8")) 86 res map { r => "%02x".format(r) } mkString "" 87 } 88 89 def get(ws: NingWSClient)(url: String, query: Map[String, String], header: Map[String, String]) = { 90 ws.url(url).withQueryString(query.toList: _*).withHeaders(header.toList: _*).get 91 } 92}

これを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のようなものは使いたくないです.
どこを修正したらよいのか教えてください.

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

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)))) }

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

投稿2017/01/06 15:21

Zomathi

総合スコア26

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問