英文:
How to exchange OAUTH token ( Go ) using Google API
问题
这是我目前的代码,返回了一个"400错误"。我做错了什么吗?我无法弄清楚为什么它不起作用,因为请求非常简单明了。
package main
import (
"code.google.com/p/goauth2/oauth"
"fmt"
"log"
)
func main() {
cachefile := "cache.json"
code := "4/xxxxx.8uFT5Z0slpMbJvIeHux6iLY_9k7ajw" //从URL重定向中接收到的代码
// 设置配置。
config := &oauth.Config{
ClientId: "xx.apps.googleusercontent.com",
ClientSecret: "cWP3HudD3XmaP33j8",
RedirectURL: "https://crm.com/sender/gmail/auth/callBack",
Scope: "https://www.googleapis.com/auth/gmail.compose",
AuthURL: "https://accounts.google.com/o/oauth2/auth",
TokenURL: "https://accounts.google.com/o/oauth2/token",
AccessType: "offline",
TokenCache: oauth.CacheFile(cachefile),
}
// 使用配置设置传输。
transport := &oauth.Transport{Config: config}
token, err := config.TokenCache.Token()
if err != nil {
token, err = transport.Exchange(code)
if err != nil {
log.Fatal("Exchange:", err)
}
}
// (Exchange方法将自动缓存令牌。)
transport.Token = token
fmt.Println(token)
}
结果
Exchange:OAuthError: updateToken: Unexpected HTTP status 400 Bad Request
英文:
Here is what I have so far which returns a "400 error". Am I doing something wrong? I can't figure it out why is not working as the request is pretty straightforward
package main
import (
"code.google.com/p/goauth2/oauth"
"fmt"
"log"
)
func main() {
cachefile := "cache.json"
code := "4/xxxxx.8uFT5Z0slpMbJvIeHux6iLY_9k7ajw" //the code received from the URL redirect
// Set up a configuration.
config := &oauth.Config{
ClientId: "xx.apps.googleusercontent.com",
ClientSecret: "cWP3HudD3XmaP33j8",
RedirectURL: "https://crm.com/sender/gmail/auth/callBack",
Scope: "https://www.googleapis.com/auth/gmail.compose",
AuthURL: "https://accounts.google.com/o/oauth2/auth",
TokenURL: "https://accounts.google.com/o/oauth2/token",
AccessType: "offline",
TokenCache: oauth.CacheFile(cachefile),
}
// Set up a Transport using the config.
transport := &oauth.Transport{Config: config}
token, err := config.TokenCache.Token()
if err != nil {
token, err = transport.Exchange(code)
if err != nil {
log.Fatal("Exchange:", err)
}
}
// (The Exchange method will automatically cache the token.)
transport.Token = token
fmt.Println(token)
}
Result
Exchange:OAuthError: updateToken: Unexpected HTTP status 400 Bad Request
答案1
得分: 2
我建议使用“一次性代码流”,如文档中所述:
要充分利用Google+登录的所有优势,您必须使用混合服务器端流程,在客户端使用JavaScript API客户端授权您的应用程序,并将特殊的一次性授权代码发送到您的服务器。您的服务器使用这个一次性代码来获取自己的访问令牌和刷新令牌,以便服务器能够在用户离线时进行自己的API调用。与纯服务器端流程和将访问令牌发送到服务器相比,这种一次性代码流程具有安全优势。
由于代码只能使用一次,因此破坏用户帐户的可能性较小。
客户端代码非常简单,可以按照第3步中的示例进行操作。
对于服务器端,我建议使用包oauth2而不是goauth2
。
$ go get code.google.com/p/google-api-go-client/plus/v1
$ go get github.com/golang/oauth2
$ go get google.golang.org/appengine
由于某种原因,oauth2
包还需要appengine
包。
可以使用函数NewTransportWithCode来将一次性代码交换为可重用令牌:
func exchangeCode(code string) (*oauth2.Token, *oauth2.Transport, error) {
config, err := google.NewConfig(&oauth2.Options{
ClientID: CLIENT_ID,
ClientSecret: CLIENT_SECRET,
RedirectURL: "postmessage",
Scopes: []string{"https://www.googleapis.com/auth/plus.login"},
})
if err != nil {
return &oauth2.Token{}, &oauth2.Transport{}, err
}
transport, err := config.NewTransportWithCode(code)
if err != nil {
return &oauth2.Token{}, &oauth2.Transport{}, err
}
token := transport.Token()
return token, transport, nil
}
最后,在第3步中创建的代码可以将一次性代码提交给监听/oauth2
的处理程序:
func oauth2Handler(w http.ResponseWriter, r *http.Request) {
// TODO 检查请求是否具有...
// - 方法:POST
// - 内容类型:application/octet-stream; charset=utf-8
// - CSRF令牌 http://goo.gl/mNCjJm
body, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
code := string(body[:])
token, transport, err := exchangeCode(code)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 从这里开始可以使用transport
client := http.Client{Transport: transport}
service, err := plus.New(&client)
if err != nil {
return nil, err
}
// https://www.googleapis.com/plus/v1/people/me
person, err := service.People.Get("me").Do()
// ...
}
func main() {
http.HandleFunc("/oauth2", oauth2Handler)
log.Fatal(http.ListenAndServe(":8000", nil))
}
一些错误处理被省略了,但你可以理解这个思路。
英文:
I would recommend to use 'one-time code flow', as described in the documentation:
> To take advantage of all of the benefits of Google+ Sign-In you must use a hybrid server-side flow where a user authorizes your app on the client side using the JavaScript API client and you send a special one-time authorization code to your server. Your server exchanges this one-time-use code to acquire its own access and refresh tokens from Google for the server to be able to make its own API calls, which can be done while the user is offline. This one-time code flow has security advantages over both a pure server-side flow and over sending access tokens to your server.
As code can be used just once there are less chances of compromising user's account.
Client code is pretty straight forward, follow the example in step 3.
For server-side, I would recommend using the package oauth2 instead of goauth2
.
$ go get code.google.com/p/google-api-go-client/plus/v1
$ go get github.com/golang/oauth2
$ go get google.golang.org/appengine
For some reason, oauth2
package requires also appengine
package.
Exchanging the one-time code for a reusable token can be done using function NewTransportWithCode:
func exchangeCode(code string) (*oauth2.Token, *oauth2.Transport, error) {
config, err := google.NewConfig(&oauth2.Options{
ClientID: CLIENT_ID,
ClientSecret: CLIENT_SECRET,
RedirectURL: "postmessage",
Scopes: []string{"https://www.googleapis.com/auth/plus.login"},
})
if err != nil {
return &oauth2.Token{}, &oauth2.Transport{}, err
}
transport, err := config.NewTransportWithCode(code)
if err != nil {
return &oauth2.Token{}, &oauth2.Transport{}, err
}
token := transport.Token()
return token, transport, nil
}
And finally the code you created in step 3 can submit one time code to a handler listening at /oauth2
:
func oauth2Handler(w http.ResponseWriter, r *http.Request) {
// TODO Check request has...
// - Method: POST
// - Content-Type: application/octet-stream; charset=utf-8
// - CSRF Token http://goo.gl/mNCjJm
body, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
code := string(body[:])
token, transport, err := exchangeCode(code)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// From here you can use the transport
client := http.Client{Transport: transport}
service, err := plus.New(&client)
if err != nil {
return nil, err
}
// https://www.googleapis.com/plus/v1/people/me
person, err := service.People.Get("me").Do()
// ...
}
func main() {
http.HandleFunc("/oauth2", oauth2Handler)
log.Fatal(http.ListenAndServe(":8000", nil))
}
Some error handling is missing, but you get the idea.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论