Scala+sttp调用X API v2 OAuth1.0授权遇401错误求助
排查X API v2 OAuth 1.0授权401错误(Scala + sttp场景)
OAuth 1.0的401 Unauthorized错误90%以上出在签名环节,结合你提到的sha1sign方法,重点排查以下几个核心点:
1. 参数编码是否严格符合规范
OAuth要求所有参与签名的字符串(参数、URL、密钥)必须用application/x-www-form-urlencoded编码,注意:
- 空格必须转成
%20而非+ - 特殊字符(如
*)要转成%2A - 别直接用Java原生
URLEncoder,它默认会把空格转成+,需要手动替换:
def urlEncode(s: String): String = { java.net.URLEncoder.encode(s, "UTF-8") .replace("+", "%20") .replace("*", "%2A") }
检查你的sha1sign方法里,是否所有参与签名的字符串都做了上述编码。
2. 签名基础串构造是否正确
签名基础串必须严格按HTTP方法→编码后的基础URL→编码后的排序参数的顺序用&拼接:
// 示例构造逻辑 val sortedParams = params.toList.sortBy(_._1) // 必须按字典序排序 val paramStr = sortedParams.map { case (k, v) => s"${urlEncode(k)}=${urlEncode(v)}" }.mkString("&") val baseString = s"${urlEncode(method)}&${urlEncode(baseUrl)}&${urlEncode(paramStr)}"
注意:
- HTTP方法要大写(如
POST) - 基础URL是不带参数的原始地址(比如
https://api.x.com/2/tweets) - 参数必须按字典序排序(ASCII码顺序)
3. HMAC-SHA1密钥是否正确
签名密钥是编码后的consumerSecret&编码后的tokenSecret,即使tokenSecret为空,也要保留末尾的&:
val signingKey = s"${urlEncode(consumerSecret)}&${urlEncode(tokenSecret)}"
很多人会漏掉其中一个密钥的编码,或者空tokenSecret时省略&,这会直接导致签名失效。
4. 签名的Base64编码是否规范
HMAC-SHA1生成的字节数组必须转成无换行的Base64字符串:
def sha1Sign(baseString: String, signingKey: String): String = { val mac = Mac.getInstance("HmacSHA1") val secretKey = new SecretKeySpec(signingKey.getBytes("UTF-8"), "HmacSHA1") mac.init(secretKey) val signatureBytes = mac.doFinal(baseString.getBytes("UTF-8")) // 用无换行的Base64编码器 java.util.Base64.getEncoder.encodeToString(signatureBytes) }
避免使用带换行的Base64编码实现,这会导致签名格式错误。
5. Authorization请求头格式是否合规
OAuth头必须严格按格式拼接,每个参数用双引号包裹,参数间用逗号+空格分隔:
val authHeader = s"""OAuth oauth_consumer_key="${urlEncode(consumerKey)}", oauth_nonce="${nonce}", oauth_signature="${urlEncode(signature)}", oauth_signature_method="HMAC-SHA1", oauth_timestamp="${timestamp}", oauth_token="${urlEncode(token)}", oauth_version="1.0""""
重点注意:oauth_signature必须再次编码后放入头中。
调试建议
打印出baseString、signingKey、signature三个核心值,和X官方的OAuth签名校验工具(本地计算对比)做匹配,就能快速定位哪一步出错。
内容的提问来源于stack exchange,提问作者Christian H. Kuhn
相关产品推荐
相关产品推荐

