欢迎各位程序员!在本教程中,我们将研究如何使用 go -oauth2/oauth2包实现您自己的 OAuth2 服务器和客户端 。
毫无疑问,这是评论者在我的 YouTube 视频中最常要求的话题之一,当然我自己也觉得非常有趣。
毫无疑问,对于任何面向公共甚至私有的服务或 API 来说,安全性是一项非常重要的功能,您需要非常注意才能使其正确。
理论
因此,在我们深入研究如何对其进行编码之前,了解它在后台是如何工作的很重要。通常,我们client会首先向resource owner. 所述resource owner然后授予或拒绝该请求。
有了这个authorization grant, client 然后将这个传递给 , authorization server这将授予一个 access token . 有了这个access token,我们client就可以访问受保护的资源,例如 API 或服务。
所以,话虽如此,现在让我们看看如何authorization server使用这个 go-oauth2/oauth2 包来实现我们自己的 。
一个简单的 Oauth2 流程
我们将首先基于他们在文档中提供的示例来实现一个非常简单的服务器。当我们将 anclient id和 a 传递client secret给 our 时,authorization server它应该返回我们的 access tokenthat 看起来像这样:
{"access_token":"Z_1QUVC5M_EOCESISKW8AQ","expires_in":7200,"scope":"read","token_type":"Bearer"}
所以,让我们深入研究我们的服务器实现,看看我们是否可以破译发生了什么:
package main
import (
"log"
"net/http"
"net/url"
"os"
"github.com/go-session/session"
"gopkg.in/oauth2.v3/errors"
"gopkg.in/oauth2.v3/manage"
"gopkg.in/oauth2.v3/models"
"gopkg.in/oauth2.v3/server"
"gopkg.in/oauth2.v3/store"
)
func main() {
manager := manage.NewDefaultManager()
// token store
manager.MustTokenStorage(store.NewMemoryTokenStore())
clientStore := store.NewClientStore()
clientStore.Set("222222", &models.Client{
ID: "222222",
Secret: "22222222",
Domain: "#34;,
})
manager. Map ClientStorage(clientStore)
srv := server.NewServer(server.NewConfig(), manager)
srv.SetUserAuthorizationHandler(userAuthorizeHandler)
srv.SetInternalErrorHandler(func(err error) (re *errors.Response) {
log.Println("Internal Error:", err.Error())
return
})
srv.SetResponseErrorHandler(func(re *errors.Response) {
log.Println("Response Error:", re.Error. err or())
})
http.HandleFunc("/login", loginHandler)
http.HandleFunc("/auth", authHandler)
http.HandleFunc("/authorize", func(w http.ResponseWriter, r *http. Request ) {
err := srv.HandleAuthorizeRequest(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
})
http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
err := srv.HandleTokenRequest(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
log.Println("Server is running at 9096 port.")
log.Fatal(http.ListenAndServe(":9096", nil ))
}
func userAuthorizeHandler(w http.ResponseWriter, r *http.Request) (userID string, err error) {
store, err := session.Start(nil, w, r)
if err != nil {
return
}
uid, ok := store.Get("UserID")
if !ok {
if r.Form == nil {
r.ParseForm()
}
store.Set("ReturnUri", r.Form)
store.Save()
w.Header().Set("Location", "/login")
w.WriteHeader(http.StatusFound)
return
}
userID = uid.(string)
store.Delete("UserID")
store.Save()
return
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
store, err := session.Start(nil, w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if r.Method == "POST" {
store.Set("LoggedInUserID", "000000")
store.Save()
w.Header().Set("Location", "/auth")
w.WriteHeader(http.StatusFound)
return
}
outputHTML(w, r, " static /login.html")
}
func authHandler(w http.ResponseWriter, r *http.Request) {
store, err := session.Start(nil, w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, ok := store.Get("LoggedInUserID"); !ok {
w.Header().Set("Location", "/login")
w.WriteHeader(http.StatusFound)
return
}
if r.Method == "POST" {
var form url.Values
if v, ok := store.Get("ReturnUri"); ok {
form = v.(url.Values)
}
u := new(url.URL)
u.Path = "/authorize"
u.RawQuery = form.Encode()
w.Header().Set("Location", u.String())
w.WriteHeader(http.StatusFound)
store.Delete("Form")
if v, ok := store.Get("LoggedInUserID"); ok {
store.Set("UserID", v)
}
store.Save()
return
}
outputHTML(w, r, "static/auth.html")
}
func outputHTML(w http.ResponseWriter, req *http.Request, filename string) {
file, err := os.Open(filename)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
defer file.Close()
fi, _ := file.Stat()
http.ServeContent(w, req, file.Name(), fi.ModTime(), file)
}
我们的客户
现在我们已经完成了我们的服务器实现,我们可以专注于构建我们的客户端。这将使用golang.org/x/oauth2标准包进行身份验证。
我们将定义一个非常简单的服务器,net/http它具有 2 个端点:
- /- 我们客户的根或主页
- /oauth2- 成功验证客户端的路由将自动重定向到。
我们将首先定义我们的oauth2.Config{}对象,该对象将包含我们的 ClientIDor ClientSecret。我们的 OAuth2 服务器实现已经记录了这两个变量,如果它们不匹配,我们将无法从我们的服务器检索访问令牌。
它还将接受一个 字符串 ,Scopes其中定义了我们的访问令牌的范围,这些范围可以定义对给定资源的各种不同级别的访问。例如,我们可以提供定义一个Read-Only范围,它只为客户端提供对我们底层资源的只读访问。
接下来,我们定义RedirectURLwhich 指定了授权服务器在成功验证后应重定向到的端点。我们希望这由我们的/oauth2端点处理。
最后,我们确定oauth2.Endpoint这需要在AuthURL和 TokenURL会朝着我们的授权,我们在我们的服务器上预先定义的令牌端点点。
package main
import (
"context"
" encoding /json"
"fmt"
"log"
"net/http"
"golang.org/x/oauth2"
)
var (
config = oauth2.Config{
ClientID: "222222",
ClientSecret: "22222222",
Scopes: []string{"all"},
Redirect URL: "#34;,
// This points to our Authorization Server
// if our Client ID and Client Secret are valid
// it will attempt to authorize our user
Endpoint: oauth2.Endpoint{
AuthURL: "#34;,
TokenURL: "#34;,
},
}
)
// Homepage
func HomePage(w http.ResponseWriter, r *http.Request) {
fmt.Println("Homepage Hit!")
u := config.AuthCodeURL("xyz")
http.Redirect(w, r, u, http.StatusFound)
}
// Authorize
func Authorize(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
state := r.Form.Get("state")
if state != "xyz" {
http.Error(w, "State invalid", http.StatusBadRequest)
return
}
code := r.Form.Get("code")
if code == "" {
http.Error(w, "Code not found", http.StatusBadRequest)
return
}
token, err := config.Exchange(context.Background(), code)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
e := json.NewEncoder(w)
e.SetIndent("", " ")
e.Encode(*token)
}
func main() {
// 1 - We attempt to hit our Homepage route
// if we attempt to hit this unauthenticated, it
// will automatically redirect to our Auth
// server and prompt for login credentials
http.HandleFunc("/", HomePage)
// 2 - This displays our state, code and
// token and expiry time that we get back
// from our Authorization server
http.HandleFunc("/oauth2", Authorize)
// 3 - We start up our Client on port 9094
log.Println("Client is running at 9094 port.")
log.Fatal(http.ListenAndServe(":9094", nil))
}
所以,我们已经成功地建立了我们的客户。让我们尝试运行它,看看会发生什么。
$ go run main.go
2018/10/20 13:25:22 Client is running at 9094 port.
现在,每当您localhost:9094在浏览器中点击时,您应该会看到它自动重定向到您正在运行的服务器实现, localhost:9096/login. 然后我们将提供我们的凭据admin并admin 用于演示目的,这将提示我们授予对客户的访问权限。
当我们点击Allow它会自动重定向我们回到我们的客户端应用程序/oauth2的端点,但它会返回一个包含JSON字符串我们 access_token,refresh_token,token_type当我们的令牌将到期。
太棒了,我们实现了一个完全有效的 Oauth2 流程。
结论
因此,在本教程中,我们研究了如何authorization server在 Go 中实现自己 的。然后我们研究了如何构建一个简单的基于 Go 的客户端,该客户端随后可以access tokens向该服务器发出请求。
希望您发现本教程很有用!如果你这样做了,请随时在下面的评论部分告诉我!