Golang实现ETrade API认证时遇到问题如何解决

Golang实现ETrade API认证时遇到问题如何解决 我已经尝试使用Golang在ETrade API上获取授权一个月了。我一直与他们的技术支持保持联系,但他们毫无帮助。他们需要一周时间才能回复,有时回复只是表示抱歉耗时这么久。最新的回复只是再次列出了API的URL。

我一直在使用这些库:

github.com/blbgo/etradeapi
github.com/dghubble/oauth1

它们使用Oauth1,所以第一步是请求一个临时令牌。然后你使用该令牌通过重定向请求下一个令牌。有几次,他们的网站要求我输入凭据以允许应用程序访问我的账户,但通常它会直接显示错误信息。

这是我的代码:

func GetOauthToken(p *data.DaemonPage, w http.ResponseWriter, req *http.Request) error {
	endpoint := oauth1.Endpoint{
		RequestTokenURL: REQUEST_TOKEN_URL,
		AuthorizeURL:    AUTHORIZE_URL,
		AccessTokenURL:  ACCESS_TOKEN_URL,
	}
	config := oauth1.Config{
		ConsumerKey:    SANDBOX_API_KEY,
		ConsumerSecret: SANDBOX_API_SECRET,
		CallbackURL:    "oob",
		Endpoint:       endpoint,
	}

	var err, err1 error
	requestToken, requestSecret, err = config.RequestToken()
	if err != nil {
		return err
	}
	fmt.Println(requestToken, requestSecret)

	authorizationURL, err1 := config.AuthorizationURL(requestToken)
	if err1 != nil {
		return err
	}

	values := authorizationURL.Query()
	fmt.Println(values)

	http.Redirect(w, req, authorizationURL.String(), http.StatusFound)

	return nil
}

当我进行重定向时,我收到这条消息:

Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again.

如果有人能提供建议,我将不胜感激。


更多关于Golang实现ETrade API认证时遇到问题如何解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

实际上,在 Postman 中它也无法工作。我尝试按照他们的建议指定了一个回调函数,但仍然收到相同的错误信息。 我甚至尝试下载了他们用于访问 etrade 的 Java 项目,但它直接报错退出。它甚至没有达到我的代码所运行到的阶段。

更多关于Golang实现ETrade API认证时遇到问题如何解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


嗨,Rick,首先声明一下,我对OAuth1或2一无所知,对HTTP也几乎不了解,但我愿意尝试提供帮助。

有没有办法从Postman中导出整个HTTP请求(我想你说过这个方法是有效的,包括请求头和请求体?我不确定HTTP是否有尾部信息,如果有的话,也包括它们)?如果可以,我建议将 (*github.com/dghubble/oauth1.Config).RequestToken 方法(或者 AuthorizationURL 方法,取决于哪个方法产生了你提到的错误)的定义复制到你包中的一个独立函数里,并添加一个(或多个)对 net/http/httputil.DumpRequestOut 的调用,将请求信息写入文件。这样你就可以同时打开Postman版本的请求,并使用diff工具进行并排比较。也许这能帮助识别Postman做了什么而Go没做,或者Go做了什么它本不应该做的事情?

我一直无法解决这个问题,因此我一直在处理项目的其他部分,现在我又回来处理它了。 昨天,我尝试在进行重定向之前,将 oauth_consumer_keyoauth_token 都添加到请求头中。我还将 oauth_consumer_key 添加到了 URL 字符串中。 当前的 URL 字符串: https://us.etrade.com/e/t/etws/authorize?oauth_token=530Tfykaz3nYIuFz9vDx2kBgI3wlNgOFuZjkM5RTNgQ%3D&oauth_consumer_key=b849e5eac42c07995b893b631c261968

这是我尝试重定向的代码:

	client := http.Client{
		Transport:     nil,
		CheckRedirect: nil,
		Jar:           nil,
		Timeout:       0,
	}

	OauthClient = oauth.Client{
		Credentials:                   oauth.Credentials{Token: PRODUCTION_API_KEY, Secret: PRODUCTION_API_SECRET},
		TemporaryCredentialRequestURI: PRODUCTION_REQUEST_TOKEN_URL,
		ResourceOwnerAuthorizationURI: AUTHORIZE_URL,
		TokenRequestURI:               ACCESS_TOKEN_URL,
		RenewCredentialRequestURI:     "",
		TemporaryCredentialsMethod:    "",
		TokenCredentailsMethod:        "POST",
		Header:                        nil,
		SignatureMethod:               0,
		PrivateKey:                    nil,
	}

	credentials, err := OauthClient.RequestTemporaryCredentials(&client, "oob", nil)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(credentials)
	requestToken = credentials.Token
	requestSecret = credentials.Secret

	authorizationURLString := OauthClient.AuthorizationURL(credentials, nil)
	authorizationURLString += "&oauth_consumer_key=" + PRODUCTION_API_KEY
	req.Header.Set("oauth_consumer_key", PRODUCTION_API_KEY)
	req.Header.Set("oauth_token", requestToken)

	http.Redirect(w, req, authorizationURLString, http.StatusTemporaryRedirect)

我发现一件非常奇怪的事情:偶尔重定向似乎会起作用,因为浏览器会按预期要求我输入 E*Trade 的用户名和密码。我输入了凭据。然后它继续显示错误信息: Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again.

我尝试在重定向之前对请求执行 httputil.DumpRequestOut,但出现错误:不支持的协议方案“”

以下是他们文档中的说明:

一旦您的应用程序获得了请求令牌,它应该将用户重定向到 E*TRADE 授权页面,如下面的授权应用程序请求 URL 所示。请注意,此 URL 包含请求令牌和消费者密钥作为参数。运行该 URL 会打开一个页面,要求用户授权该应用程序。用户批准授权请求后,E*TRADE 会生成一个验证码并将其显示在授权完成页面上。然后,用户可以手动复制该代码并将其粘贴到应用程序中。

有人对我如何解决这个问题有建议吗?

从你的描述和代码来看,ETrade API认证失败的主要问题在于OAuth1流程中的临时令牌请求或授权步骤。错误信息“Due to a logon delay or other issue”通常表示临时令牌无效、已过期或与授权请求不匹配。以下是几个关键排查点和修正后的代码示例:

1. 检查临时令牌请求的URL和参数

ETrade的沙箱环境和生产环境使用不同的URL。确保你使用的是正确的环境:

const (
    // 沙箱环境
    SANDBOX_REQUEST_TOKEN_URL = "https://apisb.etrade.com/oauth/request_token"
    SANDBOX_AUTHORIZE_URL     = "https://us.etrade.com/e/t/etws/authorize"
    SANDBOX_ACCESS_TOKEN_URL  = "https://apisb.etrade.com/oauth/access_token"
    
    // 生产环境
    PROD_REQUEST_TOKEN_URL    = "https://api.etrade.com/oauth/request_token"
    PROD_AUTHORIZE_URL        = "https://us.etrade.com/e/t/etws/authorize"
    PROD_ACCESS_TOKEN_URL     = "https://api.etrade.com/oauth/access_token"
)

2. 修正OAuth1配置

ETrade API要求OAuth1签名方法为HMAC-SHA1,且回调URL需正确处理。使用oob(Out of Band)时需确保后续手动输入验证码:

import (
    "github.com/dghubble/oauth1"
)

func GetOauthToken() error {
    config := oauth1.Config{
        ConsumerKey:    SANDBOX_API_KEY,
        ConsumerSecret: SANDBOX_API_SECRET,
        CallbackURL:    "oob", // 或你的实际回调URL
        Endpoint: oauth1.Endpoint{
            RequestTokenURL: SANDBOX_REQUEST_TOKEN_URL,
            AuthorizeURL:    SANDBOX_AUTHORIZE_URL,
            AccessTokenURL:  SANDBOX_ACCESS_TOKEN_URL,
        },
    }
    
    // 请求临时令牌
    requestToken, requestSecret, err := config.RequestToken()
    if err != nil {
        return fmt.Errorf("请求临时令牌失败: %v", err)
    }
    
    // 生成授权URL(用户需访问此URL并授权)
    authURL, err := config.AuthorizationURL(requestToken)
    if err != nil {
        return fmt.Errorf("生成授权URL失败: %v", err)
    }
    
    // 保存requestSecret供后续使用(如存入会话或数据库)
    saveTempSecret(requestToken, requestSecret)
    
    // 重定向用户到授权页面
    // 注意:生产环境中需通过HTTP重定向或返回URL给前端
    fmt.Printf("请访问以下URL授权: %s\n", authURL.String())
    return nil
}

3. 处理授权回调并获取访问令牌

用户授权后,ETrade会返回验证码(verifier),需用其交换访问令牌:

func HandleCallback(verifier string) error {
    // 从存储中获取临时令牌和密钥
    requestToken, requestSecret := loadTempSecret()
    
    config := oauth1.Config{
        ConsumerKey:    SANDBOX_API_KEY,
        ConsumerSecret: SANDBOX_API_SECRET,
        CallbackURL:    "oob",
        Endpoint: oauth1.Endpoint{
            RequestTokenURL: SANDBOX_REQUEST_TOKEN_URL,
            AuthorizeURL:    SANDBOX_AUTHORIZE_URL,
            AccessTokenURL:  SANDBOX_ACCESS_TOKEN_URL,
        },
    }
    
    // 创建临时令牌的OAuth1 Token
    tempToken := oauth1.NewToken(requestToken, requestSecret)
    
    // 交换访问令牌
    accessToken, accessSecret, err := config.AccessToken(tempToken, requestSecret, verifier)
    if err != nil {
        return fmt.Errorf("获取访问令牌失败: %v", err)
    }
    
    fmt.Printf("访问令牌: %s\n访问密钥: %s\n", accessToken, accessSecret)
    return nil
}

4. 常见问题排查

  • 时间同步问题:确保服务器时间与NTP同步,OAuth1对时间戳敏感。
  • 临时令牌过期:ETrade临时令牌有效期较短(约5分钟),需快速完成授权流程。
  • 沙箱账户限制:确认你的ETrade沙箱账户已激活且API权限已启用。
  • 签名错误:使用调试工具(如Wireshark或mitmproxy)捕获请求,对比ETrade文档中的签名示例。

5. 完整流程示例

package main

import (
    "fmt"
    "github.com/dghubble/oauth1"
)

var (
    tempSecrets = map[string]string{} // 实际应用中应使用持久化存储
)

func main() {
    // 步骤1:获取临时令牌并重定向用户
    if err := initiateAuth(); err != nil {
        panic(err)
    }
    
    // 步骤2:用户授权后,从回调中获取verifier(假设为"123456")
    verifier := "123456" // 实际应从回调URL参数获取
    if err := completeAuth(verifier); err != nil {
        panic(err)
    }
}

func initiateAuth() error {
    config := oauth1.Config{
        ConsumerKey:    "your_consumer_key",
        ConsumerSecret: "your_consumer_secret",
        CallbackURL:    "oob",
        Endpoint: oauth1.Endpoint{
            RequestTokenURL: "https://apisb.etrade.com/oauth/request_token",
            AuthorizeURL:    "https://us.etrade.com/e/t/etws/authorize",
            AccessTokenURL:  "https://apisb.etrade.com/oauth/access_token",
        },
    }
    
    requestToken, requestSecret, err := config.RequestToken()
    if err != nil {
        return err
    }
    
    tempSecrets[requestToken] = requestSecret
    
    authURL, _ := config.AuthorizationURL(requestToken)
    fmt.Printf("请访问以下URL完成授权:\n%s\n", authURL.String())
    return nil
}

func completeAuth(verifier string) error {
    // 注意:这里需要从存储中获取对应的requestToken
    requestToken := "从存储或上下文获取"
    requestSecret := tempSecrets[requestToken]
    
    config := oauth1.Config{
        ConsumerKey:    "your_consumer_key",
        ConsumerSecret: "your_consumer_secret",
        CallbackURL:    "oob",
        Endpoint: oauth1.Endpoint{
            RequestTokenURL: "https://apisb.etrade.com/oauth/request_token",
            AuthorizeURL:    "https://us.etrade.com/e/t/etws/authorize",
            AccessTokenURL:  "https://apisb.etrade.com/oauth/access_token",
        },
    }
    
    tempToken := oauth1.NewToken(requestToken, requestSecret)
    accessToken, accessSecret, err := config.AccessToken(tempToken, requestSecret, verifier)
    if err != nil {
        return err
    }
    
    fmt.Printf("认证成功!\n访问令牌: %s\n访问密钥: %s\n", accessToken, accessSecret)
    return nil
}

如果问题仍然存在,建议在请求临时令牌后立即打印完整的授权URL,手动在浏览器中访问该URL,观察ETrade返回的具体错误页面信息。这有助于区分是OAuth配置问题还是ETrade账户/权限问题。

回到顶部