详解Go-JWT-RESTful身份认证教程

  

详解Go-JWT-RESTful身份认证教程

介绍

本文将详细介绍如何使用Go语言实现一套基于JWT(JSON Web Token)的RESTful身份认证系统。RESTful是一种基于HTTP协议、符合REST原则的接口设计风格,使用JWT进行身份认证和授权可以轻松实现API的状态无关性和安全性。

实现步骤

生成JWT Token

生成JWT Token是RESTful身份认证系统的第一步。JWT Token可以在身份验证后与请求一起发送到API端点。Token包含已验证用户的信息,以及一些指定的元数据,并且这些元数据是经过数字签名的。我们可以使用golang-jwt库来生成JWT Token,具体操作如下:

import (
    "github.com/dgrijalva/jwt-go"
)

type User struct {
    UserID int
    Name string
    Email string
}

func generateJWT(user *User, privateKey []byte) (string, error) {
    token := jwt.New(jwt.SigningMethodHS256)
    claims := token.Claims.(jwt.MapClaims)
    claims["userID"] = user.UserID
    claims["name"] = user.Name
    claims["email"] = user.Email
    tokenString, err := token.SignedString(privateKey)
    if err != nil {
        return "", err
    }

    return tokenString, nil
}

将上述代码复制到项目中并进行一些必要的修改,例如:对传递进来的privateKey变量进行替换。

在上述代码中,我们先创建一个JWT Token对象,使用HS256算法对Token进行签名。我们还设置了一些已验证用户的信息。最后,我们使用私钥对Token进行签名,在调用SignedString函数时,私钥会自动应用于Token签名。最终,通过generateJWT函数返回的Token String将用于与其他API请求一起发送到API端点。

验证JWT Token

使用RESTful身份认证系统时,一旦JWT Token发出,接下来的步骤就是在每个请求上对Token进行验证,以确保Token还是有效的,且未被更改或伪造。在Go语言中,我们可以使用以下代码进行JWT Token验证:

import (
    "github.com/dgrijalva/jwt-go"
)

func validateJWT(tokenString string, secretKey []byte) (*User, error) {
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
        }
        return secretKey, nil
    })

    if err != nil {
        return nil, err
    }

    if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
        user := &User{
            UserID: claims["userID"].(int),
            Name: claims["name"].(string),
            Email: claims["email"].(string),
        }

        return user, nil
    }

    return nil, fmt.Errorf("Invalid token")
}

在上述代码中,我们使用jwt.Parse函数进行Token解析,为了确保Token有效,我们首先检查Token是否是经过HS256算法签名的。接下来,我们使用提供的私钥对Token进行验证。如果Token在验证过程中是有效的,并包含所需的元数据,则我们可以创建一个User对象并返回它。如果Token无效,或者不包含所需的元数据,则将返回“Invalid token”错误消息。

实现RESTful API

有了JWT Token的生成和验证方法后,我们现在来介绍如何实现RESTful身份认证系统。在Go语言中,可以使用gofiber库来构建RESTful API。在下面的示例中,我们将展示一个基于gofiber的RESTful API,该API仅向被身份验证的用户提供访问权限。

import (
    "github.com/gofiber/fiber/v2"
)

func main() {
    app := fiber.New()

    api := app.Group("/api")
    api.Use(authenticate)

    api.Get("/user", getCurrentUser)

    app.Listen(":3000")
}

func authenticate(c *fiber.Ctx) error {
    tokenString := c.Get("Authorization")
    if tokenString == "" {
        return fiber.ErrUnauthorized
    }

    tokenString = strings.TrimPrefix(tokenString, "Bearer ")
    user, err := validateJWT(tokenString, []byte("secretKey"))
    if err != nil {
        return fiber.ErrUnauthorized
    }

    c.Locals("user", user)
    return c.Next()
}

func getCurrentUser(c *fiber.Ctx) error {
    user := c.Locals("user").(*User)
    return c.JSON(user)
}

在上述代码中,我们首先创建了一个名为api的组,它的URL前缀是/api,并使用了authenticate中间件进行身份认证。我们将getCurrentUser函数作为响应API请求的控制器。
authenticate函数中,我们使用validateJWT函数解析并验证Bearer Token(也就是JWT Token)。如果验证成功,则在请求全局对象(也就是CTX对象)中将已验证的用户对象存储为Locals,以便其他随后的请求处理函数使用。

注意,在实际应用程序中,您需要将密钥保存在数据库中,并在每个请求上使用该密钥进行Token验证。

示例

示例1

我们可以通过使用以下cURL命令来测试我们的RESTful API:

curl -H "Authorization: Bearer <Token String>" http://localhost:3000/api/user

这将返回当前用户的信息。如果令牌无效,则API将返回401(Unauthorized)错误消息。

示例2

下面是如何使用Go语言实现一个基于RESTful身份认证系统的网站:

package main

import (
    "fmt"
    "html/template"
    "log"
    "net/http"

    "github.com/gofiber/fiber/v2"
)

func main() {
    app := fiber.New()

    app.Static("/static", "./static")

    app.Get("/", home)
    app.Post("/authenticate", authenticate)
    app.Get("/dashboard", dashboard)

    app.Listen(":3000")
}

func home(c *fiber.Ctx) error {
    return c.Render("index", nil)
}

func dashboard(c *fiber.Ctx) error {
    user := c.Locals("user").(*User)
    return c.Render("dashboard", struct{ User *User }{User: user})
}

func authenticate(c *fiber.Ctx) error {
    email := c.FormValue("email")
    password := c.FormValue("password")
    user, err := validateUser(email, password)
    if err != nil {
        return c.Render("index", struct{ Error string }{Error: "Invalid email or password"})
    }

    token, err := generateJWT(user, []byte("secretKey"))
    if err != nil {
        return c.Render("index", struct{ Error string }{Error: "Failed to generate token"})
    }

    c.Cookie(&fiber.Cookie{
        Name:  "jwt",
        Value: token,
    })

    return c.Redirect("/dashboard")
}

func validateUser(email string, password string) (*User, error) {
    // validate user's credentials
}

type User struct {
    UserID int
    Name   string
    Email  string
}

type TemplateFactory struct {
    Templates map[string]*template.Template
}

func (tf *TemplateFactory) Get(name string) (*template.Template, error) {
    if t := tf.Templates[name]; t != nil {
        return t, nil
    }

    t, err := template.ParseFiles(fmt.Sprintf("%s.html", name))
    if err != nil {
        return nil, err
    }

    tf.Templates[name] = t
    return t, nil
}

func NewTemplateFactory() *TemplateFactory {
    return &TemplateFactory{
        Templates: make(map[string]*template.Template),
    }
}

func init() {
    tf := NewTemplateFactory()

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        t, err := tf.Get("index")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        t.Execute(w, nil)
    })

    http.HandleFunc("/authenticate", func(w http.ResponseWriter, r *http.Request) {
        email := r.PostFormValue("email")
        password := r.PostFormValue("password")
        user, err := validateUser(email, password)
        if err != nil {
            http.Redirect(w, r, "/?error=invalid_credentials", http.StatusTemporaryRedirect)
            return
        }

        token, err := generateJWT(user, []byte("secretKey"))
        if err != nil {
            http.Redirect(w, r, "/?error=generate_token_failed", http.StatusTemporaryRedirect)
            return
        }

        http.SetCookie(w, &http.Cookie{
            Name:  "jwt",
            Value: token,
        })

        http.Redirect(w, r, "/dashboard", http.StatusTemporaryRedirect)
    })

    http.HandleFunc("/dashboard", func(w http.ResponseWriter, r *http.Request) {
        token, err := r.Cookie("jwt")
        if err != nil {
            http.Redirect(w, r, "/?error=unauthorized", http.StatusTemporaryRedirect)
            return
        }

        user, err := validateJWT(token.Value, []byte("secretKey"))
        if err != nil {
            http.Redirect(w, r, "/?error=validate_token_failed", http.StatusTemporaryRedirect)
            return
        }

        t, err := tf.Get("dashboard")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        t.Execute(w, struct{ User *User }{User: user})
    })

    log.Fatal(http.ListenAndServe(":3000", nil))
}

在上面的示例中,我们使用了http库而不是gofiber库,但实现原理相同。我们为网站创建了一个主页并使用JWT保护了所有需要进行身份验证的页面。我们还在authenticate路由上实现了一个用于用户身份验证的控制器。在我们验证用户身份后,我们使用generateJWT函数为该用户生成JWT Token并保存该Token用于随后的所有请求中。
最后,在dashboard中我们使用JWT Token来验证用户已登录,并在验证完成后向用户展示一个保护的用户仪表板。
这样,我们的RESTful身份认证系统就完成了搭建。

结论

本文介绍了如何使用Go语言和JWT来实现RESTful身份验证系统。我们使用gofiber库来创建API和Web App,并使用golang-jwt库来生成和验证JWT Token。本文还提供了两个示例代码,从根本上说明了如何生成JWT Token、如何验证JWT Token,并如何实现基于RESTful身份认证的Web应用程序。

相关文章