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

关键修复点:

  1. 参数编码:使用url.QueryEscape()进行单次编码,避免双重编码
  2. 时间戳处理:确保使用当前时间戳,格式为秒级Unix时间戳
  3. 签名基础字符串:正确构造method&url&params格式
  4. 签名密钥:正确处理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格式和参数顺序的差异。

回到顶部