一架梯子,一头程序猿,仰望星空!
Go Fiber教程 > 内容正文

Go Fiber JWT例子


Fiber JWT

JWT返回一个JSON Web Token (JWT)认证中间件。对于有效的令牌,它会在Ctx.Locals中设置用户并调用下一个处理程序。对于无效的令牌,它会返回“401 - Unauthorized”错误。对于缺少的令牌,它会返回“400 - Bad Request”错误。

注意:需要Go 1.19及以上版本

安装

此中间件支持Fiber v1和v2,请相应安装。

go get -u github.com/gofiber/fiber/v2
go get -u github.com/gofiber/contrib/jwt
go get -u github.com/golang-jwt/jwt/v5

签名

jwtware.New(config ...jwtware.Config) func(*fiber.Ctx) error

配置

属性 类型 描述 默认值
Filter func(*fiber.Ctx) bool 定义一个用于跳过中间件的函数 nil
SuccessHandler func(*fiber.Ctx) error SuccessHandler定义一个在有效令牌上执行的函数。 nil
ErrorHandler func(*fiber.Ctx, error) error ErrorHandler定义一个在无效令牌上执行的函数。 401 Invalid or expired JWT
SigningKey interface{} 用于验证令牌的签名密钥。如果SigningKeys的长度为0,则用作备用。 nil
SigningKeys map[string]interface{} 用于验证带有kid字段的令牌的签名密钥映射。 nil
ContextKey string 用于将令牌中的用户信息存储到上下文中的上下文键。 "user"
Claims jwt.Claim Claims是扩展的声明数据,定义了令牌内容。 jwt.MapClaims{}
TokenLookup string TokenLookup是一个字符串,形如:,用于解析令牌。 "header:Authorization"
AuthScheme string 在Authorization header中使用的AuthScheme。默认值("Bearer")仅与默认的TokenLookup值一起使用。 "Bearer"
KeyFunc func() jwt.Keyfunc KeyFunc定义用户定义的函数,用于提供用于令牌验证的公钥。 jwtKeyFunc
JWKSetURLs []string 用于解析JWT的唯一JSON Web Key (JWK) Set URL的切片。 nil

HS256 示例

package main

import (
    "time"

    "github.com/gofiber/fiber/v2"

    jwtware "github.com/gofiber/contrib/jwt"
    "github.com/golang-jwt/jwt/v5"
)

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

    // 登录路由
    app.Post("/login", login)

    // 未经身份认证的路由
    app.Get("/", accessible)

    // JWT 中间件
    app.Use(jwtware.New(jwtware.Config{
        SigningKey: jwtware.SigningKey{Key: []byte("secret")},
    }))

    // 受限制的路由
    app.Get("/restricted", restricted)

    app.Listen(":3000")
}

func login(c *fiber.Ctx) error {
    user := c.FormValue("user")
    pass := c.FormValue("pass")

    // 抛出未授权错误
    if user != "john" || pass != "doe" {
        return c.SendStatus(fiber.StatusUnauthorized)
    }

    // 创建声明
    claims := jwt.MapClaims{
        "name":  "John Doe",
        "admin": true,
        "exp":   time.Now().Add(time.Hour * 72).Unix(),
    }

    // 创建令牌
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

    // 生成编码的令牌并作为响应发送
    t, err := token.SignedString([]byte("secret"))
    if err != nil {
        return c.SendStatus(fiber.StatusInternalServerError)
    }

    return c.JSON(fiber.Map{"token": t})
}

func accessible(c *fiber.Ctx) error {
    return c.SendString("可访问")
}

func restricted(c *fiber.Ctx) error {
    user := c.Locals("user").(*jwt.Token)
    claims := user.Claims.(jwt.MapClaims)
    name := claims["name"].(string)
    return c.SendString("欢迎 " + name)
}

HS256 测试

使用用户名和密码登录以获取令牌。

curl --data "user=john&pass=doe" http://localhost:3000/login

响应

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"
}

使用授权请求头中的令牌请求受限资源。

curl localhost:3000/restricted -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"

响应

欢迎 John Doe

RS256示例

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "log"
    "time"

    "github.com/gofiber/fiber/v2"

    "github.com/golang-jwt/jwt/v5"

    jwtware "github.com/gofiber/contrib/jwt"
)

var (
    // 显然,这只是一个测试示例。不要在生产中这样做。
    // 在生产中,你应该事先生成密钥对。
    // 绝不要将私钥添加到任何GitHub仓库中。
    privateKey *rsa.PrivateKey
)

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

    // 为了示范,在每次运行时生成一个新的私钥/公钥对。参见上面的说明。
    rng := rand.Reader
    var err error
    privateKey, err = rsa.GenerateKey(rng, 2048)
    if err != nil {
        log.Fatalf("rsa.GenerateKey:%v", err)
    }

    // 登录路由
    app.Post("/login", login)

    // 未经身份验证的路由
    app.Get("/", accessible)

    // JWT中间件
    app.Use(jwtware.New(jwtware.Config{
        SigningKey: jwtware.SigningKey{
            JWTAlg: jwtware.RS256,
            Key:    privateKey.Public(),
        },
    }))

    // 受限制的路由
    app.Get("/restricted", restricted)

    app.Listen(":3000")
}

func login(c *fiber.Ctx) error {
    user := c.FormValue("user")
    pass := c.FormValue("pass")

    // 抛出未授权错误
    if user != "john" || pass != "doe" {
        return c.SendStatus(fiber.StatusUnauthorized)
    }

    // 创建Claims
    claims := jwt.MapClaims{
        "name":  "John Doe",
        "admin": true,
        "exp":   time.Now().Add(time.Hour * 72).Unix(),
    }

    // 创建token
    token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)

    // 生成编码的token并将其作为响应发送。
    t, err := token.SignedString(privateKey)
    if err != nil {
        log.Printf("token.SignedString:%v", err)
        return c.SendStatus(fiber.StatusInternalServerError)
    }

    return c.JSON(fiber.Map{"token": t})
}

func accessible(c *fiber.Ctx) error {
    return c.SendString("可访问")
}

func restricted(c *fiber.Ctx) error {
    user := c.Locals("user").(*jwt.Token)
    claims := user.Claims.(jwt.MapClaims)
    name := claims["name"].(string)
    return c.SendString("欢迎 " + name)
}

RS256测试

RS256实际上与上述HS256测试相同。

JWK Set测试

这些测试与上述基本JWT测试相同,但是需要提供JWK Set格式的有效公钥集合的JWKSetURLs。

自定义KeyFunc示例

KeyFunc定义了一个用户定义的函数,用于提供用于验证令牌的公钥。该函数需要负责验证签名算法并选择正确的密钥。如果令牌由外部方发行,则用户定义的KeyFunc可能会很有用。

当提供了用户定义的KeyFunc时,SigningKey、SigningKeys和SigningMethod将被忽略。这是三种提供令牌验证密钥的选项之一。优先顺序是用户定义的KeyFunc、SigningKeys和SigningKey。如果SigningKeys和SigningKey都未提供,则必须提供该函数。默认为验证签名算法并选择适当密钥的内部实现。

package main

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

  jwtware "github.com/gofiber/contrib/jwt"
  "github.com/golang-jwt/jwt/v5"
)

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

    app.Use(jwtware.New(jwtware.Config{
        KeyFunc: customKeyFunc(),
    }))

    app.Get("/ok", func(c *fiber.Ctx) error {
        return c.SendString("OK")
    })
}

func customKeyFunc() jwt.Keyfunc {
    return func(t *jwt.Token) (interface{}, error) {
        // 检查签名方法是否正确
        if t.Method.Alg() != jwtware.HS256 {
            return nil, fmt.Errorf("Unexpected jwt signing method=%v", t.Header["alg"])
        }

        // TODO 自定义加载签名密钥的实现,例如从数据库中加载
    signingKey := "secret"

        return []byte(signingKey), nil
    }
}