You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang调用Twitter API返回Unauthorized错误求助

解决Golang调用Twitter API返回401 Unauthorized的问题

核心问题分析

你代码里的OAuth参数是硬编码的,而Twitter API的OAuth 1.0a要求每次请求必须生成全新的动态参数,尤其是以下几个关键项:

  • oauth_timestamp:必须是当前请求的Unix时间戳(秒级),硬编码的旧时间戳会直接失效
  • oauth_nonce:每次请求的随机唯一字符串,重复的nonce+timestamp组合会被拒绝
  • oauth_signature:基于请求方法、URL、所有OAuth参数、请求体(如果是POST)计算的HMAC-SHA1签名,硬编码的签名完全不匹配当前请求的上下文

另外,你代码里的请求体是{},如果Postman里实际发送的是有内容的payload(比如包含text字段的推文内容),签名计算时的请求体哈希也会不匹配,导致授权失败。

具体修正步骤

1. 动态生成基础OAuth参数

每次请求前生成新的timestamp、nonce:

import (
    "crypto/rand"
    "encoding/base64"
    "strconv"
    "time"
)

// 生成随机nonce
func generateNonce() string {
    b := make([]byte, 16)
    rand.Read(b)
    return base64.URLEncoding.EncodeToString(b)
}

// 获取当前时间戳(秒)
func getTimestamp() string {
    return strconv.FormatInt(time.Now().Unix(), 10)
}

2. 正确计算OAuth签名

OAuth 1.0a签名需要以下步骤:

  1. 收集所有OAuth参数(包括consumer key、token、timestamp、nonce等)和请求体的哈希(oauth_body_hash,POST请求必须包含)
  2. 对参数按键名排序,拼接成规范化的参数字符串
  3. 拼接签名基串:请求方法&URL编码后的请求URL&URL编码后的规范化参数字符串
  4. 用consumer_secret&oauth_token_secret作为密钥,对基串做HMAC-SHA1加密,结果base64编码后再URL编码得到签名

示例代码片段(替换为你的真实密钥):

import (
    "crypto/hmac"
    "crypto/sha1"
    "encoding/base64"
    "fmt"
    "net/url"
    "sort"
    "strings"
)

// 替换为你的真实密钥
consumerKey := "your_consumer_key"
consumerSecret := "your_consumer_secret"
oauthToken := "your_oauth_token"
oauthTokenSecret := "your_oauth_token_secret"

// 真实请求体(和Postman保持完全一致)
payloadStr := `{"text":"Hello from Golang!"}`
payload := strings.NewReader(payloadStr)

// 计算请求体的SHA1哈希作为oauth_body_hash
bodyHash := sha1.Sum([]byte(payloadStr))
oauthBodyHash := base64.StdEncoding.EncodeToString(bodyHash[:])

// 生成动态参数
timestamp := getTimestamp()
nonce := generateNonce()

// 收集所有OAuth参数
oauthParams := url.Values{
    "oauth_consumer_key":     {consumerKey},
    "oauth_token":            {oauthToken},
    "oauth_signature_method": {"HMAC-SHA1"},
    "oauth_timestamp":        {timestamp},
    "oauth_nonce":            {nonce},
    "oauth_version":          {"1.0"},
    "oauth_body_hash":        {oauthBodyHash},
}

// 规范化参数:按键排序,拼接成key=value&key=value格式
sortedKeys := make([]string, 0, len(oauthParams))
for k := range oauthParams {
    sortedKeys = append(sortedKeys, k)
}
sort.Strings(sortedKeys)
var normalizedParams string
for i, k := range sortedKeys {
    if i > 0 {
        normalizedParams += "&"
    }
    normalizedParams += url.QueryEscape(k) + "=" + url.QueryEscape(oauthParams[k][0])
}

// 构建签名基串
baseURL := "https://api.twitter.com/2/tweets"
baseString := fmt.Sprintf("POST&%s&%s",
    url.QueryEscape(baseURL),
    url.QueryEscape(normalizedParams),
)

// 生成签名密钥
signingKey := fmt.Sprintf("%s&%s",
    url.QueryEscape(consumerSecret),
    url.QueryEscape(oauthTokenSecret),
)

// HMAC-SHA1加密生成签名
mac := hmac.New(sha1.New, []byte(signingKey))
mac.Write([]byte(baseString))
signatureBytes := mac.Sum(nil)
oauthSignature := url.QueryEscape(base64.StdEncoding.EncodeToString(signatureBytes))

// 构建最终的Authorization头
authHeader := fmt.Sprintf(`OAuth oauth_consumer_key="%s",oauth_token="%s",oauth_signature_method="HMAC-SHA1",oauth_timestamp="%s",oauth_nonce="%s",oauth_version="1.0",oauth_body_hash="%s",oauth_signature="%s"`,
    consumerKey, oauthToken, timestamp, nonce, oauthBodyHash, oauthSignature,
)

3. 替换硬编码的Authorization头

把代码里的req.Header.Add("Authorization", "...")替换成上面生成的authHeader,同时确保请求体和Postman完全一致。

额外注意事项

  • 确保所有密钥(consumer secret、token secret)正确无误,无多余空格或复制错误
  • 确认请求的Content-Type已正确设置为application/json
  • Postman的"生成代码"功能不会自动处理动态签名逻辑,需要手动修改为上述动态生成逻辑

内容的提问来源于stack exchange,提问作者Angelo van Cleef

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 01:42:18