跳转到主要内容

标签(标签)

资源精选(342) Go开发(108) Go语言(103) Go(99) angular(82) LLM(78) 大语言模型(63) 人工智能(53) 前端开发(50) LangChain(43) golang(43) 机器学习(39) Go工程师(38) Go程序员(38) Go开发者(36) React(33) Go基础(29) Python(24) Vue(22) Web开发(20) Web技术(19) 精选资源(19) 深度学习(19) Java(18) ChatGTP(17) Cookie(16) android(16) 前端框架(13) JavaScript(13) Next.js(12) 安卓(11) 聊天机器人(10) typescript(10) 资料精选(10) NLP(10) 第三方Cookie(9) Redwoodjs(9) ChatGPT(9) LLMOps(9) Go语言中级开发(9) 自然语言处理(9) PostgreSQL(9) 区块链(9) mlops(9) 安全(9) 全栈开发(8) OpenAI(8) Linux(8) AI(8) GraphQL(8) iOS(8) 软件架构(7) RAG(7) Go语言高级开发(7) AWS(7) C++(7) 数据科学(7) whisper(6) Prisma(6) 隐私保护(6) JSON(6) DevOps(6) 数据可视化(6) wasm(6) 计算机视觉(6) 算法(6) Rust(6) 微服务(6) 隐私沙盒(5) FedCM(5) 智能体(5) 语音识别(5) Angular开发(5) 快速应用开发(5) 提示工程(5) Agent(5) LLaMA(5) 低代码开发(5) Go测试(5) gorm(5) REST API(5) kafka(5) 推荐系统(5) WebAssembly(5) GameDev(5) CMS(5) CSS(5) machine-learning(5) 机器人(5) 游戏开发(5) Blockchain(5) Web安全(5) Kotlin(5) 低代码平台(5) 机器学习资源(5) Go资源(5) Nodejs(5) PHP(5) Swift(5) devin(4) Blitz(4) javascript框架(4) Redwood(4) GDPR(4) 生成式人工智能(4) Angular16(4) Alpaca(4) 编程语言(4) SAML(4) JWT(4) JSON处理(4) Go并发(4) 移动开发(4) 移动应用(4) security(4) 隐私(4) spring-boot(4) 物联网(4) nextjs(4) 网络安全(4) API(4) Ruby(4) 信息安全(4) flutter(4) RAG架构(3) 专家智能体(3) Chrome(3) CHIPS(3) 3PC(3) SSE(3) 人工智能软件工程师(3) LLM Agent(3) Remix(3) Ubuntu(3) GPT4All(3) 软件开发(3) 问答系统(3) 开发工具(3) 最佳实践(3) RxJS(3) SSR(3) Node.js(3) Dolly(3) 移动应用开发(3) 低代码(3) IAM(3) Web框架(3) CORS(3) 基准测试(3) Go语言数据库开发(3) Oauth2(3) 并发(3) 主题(3) Theme(3) earth(3) nginx(3) 软件工程(3) azure(3) keycloak(3) 生产力工具(3) gpt3(3) 工作流(3) C(3) jupyter(3) 认证(3) prometheus(3) GAN(3) Spring(3) 逆向工程(3) 应用安全(3) Docker(3) Django(3) R(3) .NET(3) 大数据(3) Hacking(3) 渗透测试(3) C++资源(3) Mac(3) 微信小程序(3) Python资源(3) JHipster(3) 语言模型(2) 可穿戴设备(2) JDK(2) SQL(2) Apache(2) Hashicorp Vault(2) Spring Cloud Vault(2) Go语言Web开发(2) Go测试工程师(2) WebSocket(2) 容器化(2) AES(2) 加密(2) 输入验证(2) ORM(2) Fiber(2) Postgres(2) Gorilla Mux(2) Go数据库开发(2) 模块(2) 泛型(2) 指针(2) HTTP(2) PostgreSQL开发(2) Vault(2) K8s(2) Spring boot(2) R语言(2) 深度学习资源(2) 半监督学习(2) semi-supervised-learning(2) architecture(2) 普罗米修斯(2) 嵌入模型(2) productivity(2) 编码(2) Qt(2) 前端(2) Rust语言(2) NeRF(2) 神经辐射场(2) 元宇宙(2) CPP(2) 数据分析(2) spark(2) 流处理(2) Ionic(2) 人体姿势估计(2) human-pose-estimation(2) 视频处理(2) deep-learning(2) kotlin语言(2) kotlin开发(2) burp(2) Chatbot(2) npm(2) quantum(2) OCR(2) 游戏(2) game(2) 内容管理系统(2) MySQL(2) python-books(2) pentest(2) opengl(2) IDE(2) 漏洞赏金(2) Web(2) 知识图谱(2) PyTorch(2) 数据库(2) reverse-engineering(2) 数据工程(2) swift开发(2) rest(2) robotics(2) ios-animation(2) 知识蒸馏(2) 安卓开发(2) nestjs(2) solidity(2) 爬虫(2) 面试(2) 容器(2) C++精选(2) 人工智能资源(2) Machine Learning(2) 备忘单(2) 编程书籍(2) angular资源(2) 速查表(2) cheatsheets(2) SecOps(2) mlops资源(2) R资源(2) DDD(2) 架构设计模式(2) 量化(2) Hacking资源(2) 强化学习(2) flask(2) 设计(2) 性能(2) Sysadmin(2) 系统管理员(2) Java资源(2) 机器学习精选(2) android资源(2) android-UI(2) Mac资源(2) iOS资源(2) Vue资源(2) flutter资源(2) JavaScript精选(2) JavaScript资源(2) Rust开发(2) deeplearning(2) RAD(2)

欢迎各位码农!在本教程中,我们将了解如何使用 go-oauth2/oauth2 包实现自己的 OAuth2 服务器和客户端。

毫无疑问,这是评论者对我的 YouTube 视频提出的最多要求的话题之一,而且我自己也觉得非常有趣。

毫无疑问,对于任何面向公共甚至私有的服务或 API 而言,安全性都是一个非常重要的特性,并且您需要非常注意它才能使其正确。

注意 - 可以在此处找到本教程的完整 github 存储库:TutorialEdge/go-oauth-tutorial

理论


因此,在我们深入研究如何编写代码之前,了解它在后台是如何工作的很重要。通常,我们有一个客户端,它首先向资源所有者发出授权请求。然后,资源所有者要么同意要么拒绝这个请求。

使用此授权授予,客户端然后将其传递给授权服务器,授权服务器将授予访问令牌。正是有了这个授予的访问令牌,我们的客户端才能访问受保护的资源,例如 API 或服务。

话虽如此,现在让我们看看如何使用这个 go-oauth2/oauth2 包来实现我们自己的授权服务器。

注意 - 如果您有兴趣查看 Oauth2 实现遵循的 RFC,可以在此处找到:RFC-6749

一个简单的 Oauth2 流程


我们将从基于他们在文档中提供的示例实现一个非常简单的服务器开始。当我们将客户端 ID 和客户端密码传递给授权服务器时,它应该返回我们的访问令牌,如下所示:

{"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: "http://localhost:9094",
    })
    manager.MapClientStorage(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.Error())
    })

    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 标准包进行身份验证。

我们将使用具有 2 个端点的 net/http 定义一个非常简单的服务器:

  • / - 我们客户的根目录或主页
  • /oauth2 - 成功验证客户端的路由将自动重定向到。

我们将首先定义我们的 oauth2.Config{} 对象,该对象将包含我们的 ClientID 或 ClientSecret。我们的 OAuth2 服务器实现已经记录了这两个变量,如果它们不匹配,我们将无法从我们的服务器检索访问令牌。

它还将接受定义访问令牌范围的范围字符串,这些范围可以定义对给定资源的各种不同级别的访问。例如,我们可以定义一个只读范围,它只为客户端提供对我们底层资源的只读访问权限。

接下来,我们定义了 RedirectURL,它指定了我们的授权服务器在成功认证后应该重定向到的端点。我们希望这由我们的 /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"},
        RedirectURL:  "http://localhost:9094/oauth2",
        // 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:  "http://localhost:9096/authorize",
            TokenURL: "http://localhost:9096/token",
        },
    }
)

// 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 用于演示目的,这将提示我们授予客户端访问权限。

当我们点击允许时,它会自动将我们重定向回我们的客户端应用程序 /oauth2 端点,但它会返回一个 JSON 字符串,其中包含我们的 access_token、refresh_token、token_type 以及我们的令牌何时到期。

太棒了,我们实现了一个完整的 Oauth2 流程。

结论


因此,在本教程中,我们研究了如何在 Go 中实现自己的授权服务器。然后,我们研究了如何构建一个简单的基于 Go 的客户端,该客户端随后可以向该服务器发出访问令牌的请求。

希望您发现本教程很有用!如果你这样做了,请随时在下面的评论部分告诉我!

 

文章链接