Play Framework [2.4.x] OpenID和Oauth - 使用PLAINTEXT for oauth_signature_method

时间:2015-07-31 16:49:10

标签: oauth playframework-2.0 openid

根据此处的文档:https://www.playframework.com/documentation/2.4.x/ScalaOAuth

我有一个Twitter“读者”工作没有问题。我可以授权“我的应用”并按预期通过API进行调用。

我正在尝试使用FreshBooks执行类似的过程,我似乎遇到了将oauth_signature_method预设为HMAC-SHA1的问题。

当供应商对所有API调用强制执行HTTPS时,他们(仅)似乎支持oauth_signature_method

的PLAINTEXT

与供应商合作确认此事。

搜索文档和代码以查看签名方法是否以某种方式可选择,如果是,何时/何地将该更改注入代码?

如果有人绊倒了这个并找到了解决方案,我将不胜感激!

2 个答案:

答案 0 :(得分:0)

It seems that 2/3s of the solution is to hack the code as follows:

  val serviceInfo = ServiceInfo(
      "https://sample.freshbooks.com/oauth/oauth_request.php",
      "https://sample.freshbooks.com/oauth/oauth_access.php",
      "https://sample.freshbooks.com/oauth/oauth_authorize.php", KEY)

  val provider = new CommonsHttpOAuthProvider(serviceInfo.requestTokenURL, serviceInfo.accessTokenURL, serviceInfo.authorizationURL)

  val consumer = new DefaultOAuthConsumer(serviceInfo.key.key, serviceInfo.key.secret)

  consumer.setMessageSigner(new PlainTextMessageSigner())

  def retrieveAccessToken(token: RequestToken, verifier: String): Either[OAuthException, RequestToken] = {
          consumer.setTokenWithSecret(token.token, token.secret)
          try {
              provider.retrieveAccessToken(consumer, verifier);
              Right(new RequestToken(consumer.getToken(), consumer.getTokenSecret()))
          } catch {
              case ex: OAuthException => Left(ex)
          }
      }

      def retrieveRequestToken(callbackURL: String): Either[OAuthException, RequestToken] = {
        try {
            provider.retrieveRequestToken(consumer, callbackURL)
            Right(new RequestToken(consumer.getToken(), consumer.getTokenSecret()))
        } catch {
            case ex: OAuthException => Left(ex)
        }
      }

I say 2/3s because the final piece of the puzzle uses OAuthCalculator

    WS.url("https://sample.freshbooks.com/api/2.1/xml-in")
      .sign(OAuthCalculator(KEY, credentials))
      .post(data)
      .map(result => Ok(result.body))
  }

and I can't find way to change that -- I might have to rewrite the entire class or create a new one. It seems strange that we have two methods doing basically the same thing one way and a one method doing it completely differently.

This is a total hack as we should just be able to put

consumer.setMessageSigner(new PlainTextMessageSigner()) 

right into the initial code

答案 1 :(得分:0)

我在github上发布了一个可行的解决方案:https://github.com/techmag/OAuthPlainTextCalculator

为方便起见,我会在这里重复一下代码:

    class OAuthPlainTextCalculator(consumerKey: ConsumerKey, requestToken: RequestToken) extends WSSignatureCalculator with com.ning.http.client.SignatureCalculator {
  import com.ning.http.client.{ Request, RequestBuilderBase }
  import com.ning.http.util.UTF8UrlEncoder
  import com.ning.http.util.Base64

  val HEADER_AUTHORIZATION = "Authorization"
  val KEY_OAUTH_CONSUMER_KEY = "oauth_consumer_key"
  val KEY_OAUTH_NONCE = "oauth_nonce"
  val KEY_OAUTH_SIGNATURE = "oauth_signature"
  val KEY_OAUTH_SIGNATURE_METHOD = "oauth_signature_method"
  val KEY_OAUTH_TIMESTAMP = "oauth_timestamp"
  val KEY_OAUTH_TOKEN = "oauth_token"
  val KEY_OAUTH_VERSION = "oauth_version"

  val OAUTH_VERSION_1_0 = "1.0"
  val OAUTH_SIGNATURE_METHOD = "PLAINTEXT"

  protected final val nonceBuffer: Array[Byte] = new Array[Byte](16)

  override def calculateAndAddSignature(request: Request, requestBuilder: RequestBuilderBase[_]): Unit = {
    val nonce: String = generateNonce
    val timestamp: Long = System.currentTimeMillis() / 1000L
    val signature = calculateSignature(request.getMethod, request.getUrl, timestamp, nonce, request.getFormParams, request.getQueryParams)
    val headerValue = constructAuthHeader(signature, nonce, timestamp)
    requestBuilder.setHeader(HEADER_AUTHORIZATION, headerValue);
  }

  /**
   * from http://oauth.net/core/1.0/#signing_process
   * oauth_signature is set to the concatenated encoded values of the
   * Consumer Secret and Token Secret,
   * separated by a ‘&’ character (ASCII code 38),
   * even if either secret is empty.
   * The result MUST be encoded again.
   */

  def calculateSignature(method: String, baseURL: String, oauthTimestamp: Long, nonce: String, formParams: java.util.List[com.ning.http.client.Param], queryParams: java.util.List[com.ning.http.client.Param]) = {
    val signedText = new StringBuilder(100)
    signedText.append(consumerKey.secret)
    signedText.append('&');
    signedText.append(requestToken.secret)
    UTF8UrlEncoder.encode(signedText.toString)
  }

  def constructAuthHeader(signature: String, nonce: String, oauthTimestamp: Long, sb: StringBuilder = new StringBuilder) = {
    constructAuthHeader_sb(signature, nonce, oauthTimestamp).toString
  }

  def constructAuthHeader_sb(signature: String, nonce: String, oauthTimestamp: Long, sb: StringBuilder = new StringBuilder(250)) = {
    sb.synchronized {
      sb.append("OAuth ")

      sb.append(KEY_OAUTH_CONSUMER_KEY)
      sb.append("=\"")
      sb.append(consumerKey.key)
      sb.append("\", ")

      sb.append(KEY_OAUTH_TOKEN)
      sb.append("=\"")
      sb.append(requestToken.token)
      sb.append("\", ")

      sb.append(KEY_OAUTH_SIGNATURE_METHOD)
      sb.append("=\"")
      sb.append(OAUTH_SIGNATURE_METHOD)
      sb.append("\", ")

      // careful: base64 has chars that need URL encoding:
      sb.append(KEY_OAUTH_SIGNATURE)
      sb.append("=\"");
      sb.append(signature)
      sb.append("\", ")

      sb.append(KEY_OAUTH_TIMESTAMP)
      sb.append("=\"")
      sb.append(oauthTimestamp)
      sb.append("\", ")

      // also: nonce may contain things that need URL encoding (esp. when using base64):
      sb.append(KEY_OAUTH_NONCE)
      sb.append("=\"");
      sb.append(UTF8UrlEncoder.encode(nonce))
      sb.append("\", ")

      sb.append(KEY_OAUTH_VERSION)
      sb.append("=\"")
      sb.append(OAUTH_VERSION_1_0)
      sb.append("\"")
      sb
    }
  }

  def generateNonce = synchronized {
    scala.util.Random.nextBytes(nonceBuffer)
    // let's use base64 encoding over hex, slightly more compact than hex or decimals
    Base64.encode(nonceBuffer)
  }
}

object OAuthPlainTextCalculator {
  def apply(consumerKey: ConsumerKey, token: RequestToken): WSSignatureCalculator = {
    new OAuthPlainTextCalculator(consumerKey, token)
  }
}