Golang中Oauth1签名无效问题如何解决
Golang中Oauth1签名无效问题如何解决 我正在尝试使用 E-Trade API,但遗憾的是它使用的是 OAuth1。我花了一天多的时间尝试进行授权。我想知道是否有人可以查看我的值并发现问题所在。
parameterString
include_entities=true&
oauth_callback=oob&
oauth_consumer_key=a64408d1348cc0b2b7c3dbac058fd3cc&
oauth_nonce=BpLnfgDsc2WD8F2qNfHK5a84jjJkwzDkh9h2fhfUVuS9jZ8u&
oauth_signature_method=HMAC-SHA1&oauth_timestamp=1659709080&
oauth_token=&oauth_version=1.0&
status=Hello%20Ladies%20%2B%20Gentlemen%2C%20a%20signed%20OAuth%20request%21
signatureBase
GET&https%3A%2F%2Fapisb.etrade.com%2F
oauth%2Frequest_token&
include_entities%3Dtrue%26
oauth_callback%3Doob%26
oauth_consumer_key%3Da64408d1348cc0b2b7c3dbac058fd3cc%26
oauth_nonce%3DBpLnfgDsc2WD8F2qNfHK5a84jjJkwzDkh9h2fhfUVuS9jZ8u%26
oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1659709080%26
oauth_token%3D%26
oauth_version%3D1.0%26
status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520
OAuth%2520request%2521
signingKey
1550b6791d366b0dd62949cfb4ef7b85fec0d4d2144e14efb915dae00509f639&
signature
qjbsiY0zp1J7SSOR0UoRpbqgTVs=
authHeader
OAuth oauth_callback="oob",
oauth_consumer_key="a64408d1348cc0b2b7c3dbac058fd3cc", oauth_nonce="BpLnfgDsc2WD8F2qNfHK5a84jjJkwzDkh9h2fhfUVuS9jZ8u", oauth_signature="qjbsiY0zp1J7SSOR0UoRpbqgTVs%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1659709080",
oauth_token="",
oauth_version="1.0"
response:
Www-Authenticate: OAuth realm=https://etws.etrade.com/,oauth_problem=signature_invalid
我没有隐藏任何信息,因为这只是一个沙盒授权。 为了便于阅读,已插入回车符。
顺便说一句,我能够使用 Postman 获取令牌。即使我没有指定签名,Postman 肯定创建了一个,因为它能正常工作。唉,我关闭了 oauth_signature 并重试,结果失败了。重新打开它,仍然失败。它显示:
oauth_acceptable_timestamps=1659719338-1659722938
when I have the timestamp set to: 1659721096
尽管我的时间戳在那个范围内。
更多关于Golang中Oauth1签名无效问题如何解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于Golang中Oauth1签名无效问题如何解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你的OAuth1签名问题主要出在参数编码和签名基础字符串的构造上。从你提供的signatureBase可以看出,参数编码存在双重编码的问题,特别是status参数中的空格被编码为%2520而不是%20。
以下是修复后的Go代码示例:
package main
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"net/url"
"sort"
"strings"
"time"
)
func generateOAuth1Signature(consumerKey, consumerSecret, method, baseURL string, params map[string]string) (string, error) {
// 1. 收集并编码参数
encodedParams := make(map[string]string)
for k, v := range params {
encodedKey := url.QueryEscape(k)
encodedValue := url.QueryEscape(v)
encodedParams[encodedKey] = encodedValue
}
// 2. 按字母顺序排序参数
keys := make([]string, 0, len(encodedParams))
for k := range encodedParams {
keys = append(keys, k)
}
sort.Strings(keys)
// 3. 构建参数字符串
paramPairs := make([]string, 0, len(keys))
for _, k := range keys {
paramPairs = append(paramPairs, k+"="+encodedParams[k])
}
parameterString := strings.Join(paramPairs, "&")
// 4. 构建签名基础字符串
encodedURL := url.QueryEscape(baseURL)
encodedParamsStr := url.QueryEscape(parameterString)
signatureBase := method + "&" + encodedURL + "&" + encodedParamsStr
// 5. 生成签名密钥
signingKey := url.QueryEscape(consumerSecret) + "&"
if tokenSecret, ok := params["oauth_token_secret"]; ok && tokenSecret != "" {
signingKey += url.QueryEscape(tokenSecret)
}
// 6. 计算HMAC-SHA1签名
mac := hmac.New(sha1.New, []byte(signingKey))
mac.Write([]byte(signatureBase))
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return signature, nil
}
func main() {
consumerKey := "a64408d1348cc0b2b7c3dbac058fd3cc"
consumerSecret := "1550b6791d366b0dd62949cfb4ef7b85fec0d4d2144e14efb915dae00509f639"
params := map[string]string{
"include_entities": "true",
"oauth_callback": "oob",
"oauth_consumer_key": consumerKey,
"oauth_nonce": fmt.Sprintf("%d", time.Now().UnixNano()),
"oauth_signature_method": "HMAC-SHA1",
"oauth_timestamp": fmt.Sprintf("%d", time.Now().Unix()),
"oauth_token": "",
"oauth_version": "1.0",
"status": "Hello Ladies + Gentlemen, a signed OAuth request!",
}
signature, err := generateOAuth1Signature(
consumerKey,
consumerSecret,
"GET",
"https://apisb.etrade.com/oauth/request_token",
params,
)
if err != nil {
fmt.Printf("Error generating signature: %v\n", err)
return
}
fmt.Printf("Generated signature: %s\n", signature)
// 构建Authorization头
authHeader := "OAuth "
oauthParams := []string{
"oauth_callback",
"oauth_consumer_key",
"oauth_nonce",
"oauth_signature",
"oauth_signature_method",
"oauth_timestamp",
"oauth_token",
"oauth_version",
}
for i, param := range oauthParams {
value := params[param]
if param == "oauth_signature" {
value = signature
}
authHeader += param + "=\"" + url.QueryEscape(value) + "\""
if i < len(oauthParams)-1 {
authHeader += ", "
}
}
fmt.Printf("Authorization header: %s\n", authHeader)
}
关键修复点:
- 参数编码:使用
url.QueryEscape()进行单次编码,避免双重编码 - 时间戳处理:确保使用当前时间戳,格式为秒级Unix时间戳
- 签名基础字符串:正确构造
method&url¶ms格式 - 签名密钥:正确处理consumer_secret和token_secret的连接
对于时间戳范围错误,确保你的系统时间与API服务器同步:
func syncTimestamp() error {
// 可以从API获取服务器时间或使用NTP同步
resp, err := http.Head("https://apisb.etrade.com")
if err != nil {
return err
}
defer resp.Body.Close()
dateStr := resp.Header.Get("Date")
if dateStr != "" {
// 解析服务器时间并调整本地时钟
serverTime, err := time.Parse(time.RFC1123, dateStr)
if err == nil {
// 计算时间差并调整
timeDiff := serverTime.Sub(time.Now())
fmt.Printf("Time difference: %v\n", timeDiff)
}
}
return nil
}
运行修复后的代码应该能生成有效的OAuth1签名。如果仍然遇到问题,建议使用网络抓包工具比较你的请求和Postman生成的请求,检查Header格式和参数顺序的差异。

