跳到主要内容
版本: websocket_v1.x.x

Paseto

Release Discord Test

PASETO 返回 Web Token (PASETO) 认证中间件。

  • 对于有效令牌,它会在 Ctx.Locals 中设置 payload 数据并调用下一个 handler。
  • 对于无效令牌,它返回 "401 - Unauthorized" 错误。
  • 对于缺失令牌,它返回 "400 - BadRequest" 错误。

注意: 需要 Go 1.18 或更高版本

安装

此中间件支持 Fiber v2。

go get -u github.com/gofiber/fiber/v2
go get -u github.com/gofiber/contrib/paseto
go get -u github.com/o1egl/paseto

签名

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

配置

属性类型描述默认值
下一个func(*Ctx) bool定义一个函数来跳过中间件nil
SuccessHandlerfunc(*fiber.Ctx) errorSuccessHandler 定义一个函数,该函数在令牌有效时执行。c.Next()
ErrorHandlerfunc(*fiber.Ctx, error) errorErrorHandler 定义一个函数,该函数在令牌无效时执行。401 无效或过期的 PASETO
ValidatePayloadValidator定义一个函数来验证 payload 是否有效。可选。如果使用的 payload 是使用 CreateToken 函数创建的。如果令牌是使用另一个函数创建的,则必须提供此函数。nil
SymmetricKey[]byte用于加密令牌的密钥。如果存在,中间件将生成本地令牌。nil
PrivateKeyed25519.PrivateKey用于签名令牌的密钥。如果存在(以及其 PublicKey),中间件将生成公共令牌。nil
PublicKeycrypto.PublicKey用于验证令牌的公钥。如果存在(以及 PrivateKey),中间件将生成公共令牌。nil
ContextKeystring上下文键,用于将来自令牌的用户信息存储到上下文中。"auth-token"
TokenLookup[2]stringTokenLookup 是一个大小为 2 的字符串切片,用于从请求中提取令牌["header","Authorization"]

说明

使用此中间件并创建用于认证的令牌时,可以使用函数 pasetoware.CreateToken,该函数将创建令牌、对其进行加密或签名,并返回 PASETO 令牌。

在 Config 中传递 SymmetricKey 会生成本地(加密)令牌,而传递 PublicKeyPrivateKey 会生成公共(签名)令牌。

如果您想使用自己的数据结构,需要在 paseware.Config 中提供 Validate 函数,该函数将返回存储在令牌中的数据和错误。

示例

下面列出了一些示例,可以帮助您开始使用此中间件。如果此处未显示任何其他示例,请查看测试文件。

SymmetricKey

package main

import (
"time"

"github.com/gofiber/fiber/v2"
"github.com/o1egl/paseto"

pasetoware "github.com/gofiber/contrib/paseto"
)

const secretSymmetricKey = "symmetric-secret-key (size = 32)"

func main() {

app := fiber.New()

// Login route
app.Post("/login", login)

// Unauthenticated route
app.Get("/", accessible)

// Paseto Middleware with local (encrypted) token
apiGroup := app.Group("api", pasetoware.New(pasetoware.Config{
SymmetricKey: []byte(secretSymmetricKey),
TokenPrefix: "Bearer",
}))

// Restricted Routes
apiGroup.Get("/restricted", restricted)

err := app.Listen(":8088")
if err != nil {
return
}
}

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

// Throws Unauthorized error
if user != "john" || pass != "doe" {
return c.SendStatus(fiber.StatusUnauthorized)
}

// Create token and encrypt it
encryptedToken, err := pasetoware.CreateToken([]byte(secretSymmetricKey), user, 12*time.Hour, pasetoware.PurposeLocal)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}

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

func accessible(c *fiber.Ctx) error {
return c.SendString("Accessible")
}

func restricted(c *fiber.Ctx) error {
payload := c.Locals(pasetoware.DefaultContextKey).(string)
return c.SendString("Welcome " + payload)
}

测试它

使用用户名和密码登录以检索令牌。

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

响应

{
"token": "v2.local.eY7o9YAJ7Uqyo0JdyfHXKVARj3HgBhqIHckPgNIJOU6u489CXYL6bpOXbEtTB_nNM7nTFpcRVi7YAtJToxbxkkraHmE39pqjnBgkca-URgE-jhZGuhGu7ablmK-8tVoe5iY8mQqWFuJHAznTASUHh4AG55AMUcIALi6pEG28lAgVfw2azvnvbg4JOVZnjutcOVswd-ErsAuGtuEZkTmX7BfaLaO9ZvEX9cHahYPajuRjwU2TQrcpqITg-eYMNA1NuO8OVdnGf0mkUk6ElJUTZqhx4CSSylNXr7IlOwzTbUotEDAQTcNP7IRZI3VfpnRgnmtnZ5s.bnVsbAY"
}

在 Authorization 请求头中使用令牌请求受限资源。

curl localhost:8088/api/restricted -H "Authorization: Bearer v2.local.eY7o9YAJ7Uqyo0JdyfHXKVARj3HgBhqIHckPgNIJOU6u489CXYL6bpOXbEtTB_nNM7nTFpcRVi7YAtJToxbxkkraHmE39pqjnBgkca-URgE-jhZGuhGu7ablmK-8tVoe5iY8mQqWFuJHAznTASUHh4AG55AMUcIALi6pEG28lAgVfw2azvnvbg4JOVZnjutcOVswd-ErsAuGtuEZkTmX7BfaLaO9ZvEX9cHahYPajuRjwU2TQrcpqITg-eYMNA1NuO8OVdnGf0mkUk6ElJUTZqhx4CSSylNXr7IlOwzTbUotEDAQTcNP7IRZI3VfpnRgnmtnZ5s.bnVsbA"

响应

Welcome john

SymmetricKey + 自定义 Validator 回调

package main

import (
"encoding/json"
"time"

"github.com/o1egl/paseto"

pasetoware "github.com/gofiber/contrib/paseto"
)

const secretSymmetricKey = "symmetric-secret-key (size = 32)"

type customPayloadStruct struct {
Name string `json:"name"`
ExpiresAt time.Time `json:"expiresAt"`
}

func main() {

app := fiber.New()

// Login route
app.Post("/login", login)

// Unauthenticated route
app.Get("/", accessible)

// Paseto Middleware with local (encrypted) token
apiGroup := app.Group("api", pasetoware.New(pasetoware.Config{
SymmetricKey: []byte(secretSymmetricKey),
TokenPrefix: "Bearer",
Validate: func(decrypted []byte) (any, error) {
var payload customPayloadStruct
err := json.Unmarshal(decrypted, &payload)
return payload, err
},
}))

// Restricted Routes
apiGroup.Get("/restricted", restricted)

err := app.Listen(":8088")
if err != nil {
return
}
}

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

// Throws Unauthorized error
if user != "john" || pass != "doe" {
return c.SendStatus(fiber.StatusUnauthorized)
}

// Create the payload
payload := customPayloadStruct{
Name: "John Doe",
ExpiresAt: time.Now().Add(12 * time.Hour),
}

// Create token and encrypt it
encryptedToken, err := paseto.NewV2().Encrypt([]byte(secretSymmetricKey), payload, nil)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}

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

func accessible(c *fiber.Ctx) error {
return c.SendString("Accessible")
}

func restricted(c *fiber.Ctx) error {
payload := c.Locals(pasetoware.DefaultContextKey).(customPayloadStruct)
return c.SendString("Welcome " + payload.Name)
}

测试它

使用用户名和密码登录以检索令牌。

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

响应

{
"token": "v2.local.OSnDEMUndq8JpRdCD8yX-mr-Z0-Mi85Jw0ftxseiNLCbRc44Mxl5dnn-SV9Qew1n9Y44wXZwm_FG279cILJk7lYc_B_IoMCRBudJE7qMgctkD9UBM-ZRZgCX9ekJh3S1Oo6Erp7bO-omPra5.bnVsbA"
}

在 Authorization 请求头中使用令牌请求受限资源。

curl localhost:8088/api/restricted -H "Authorization: Bearer v2.local.OSnDEMUndq8JpRdCD8yX-mr-Z0-Mi85Jw0ftxseiNLCbRc44Mxl5dnn-SV9Qew1n9Y44wXZwm_FG279cILJk7lYc_B_IoMCRBudJE7qMgctkD9UBM-ZRZgCX9ekJh3S1Oo6Erp7bO-omPra5.bnVsbA"

响应

Welcome John Doe

公共私钥 (PublicPrivate Key)

package main

import (
"crypto/ed25519"
"encoding/hex"
"time"

"github.com/gofiber/fiber/v2"

pasetoware "github.com/gofiber/contrib/paseto"
)

const privateKeySeed = "e9c67fe2433aa4110caf029eba70df2c822cad226b6300ead3dcae443ac3810f"

var seed, _ = hex.DecodeString(privateKeySeed)
var privateKey = ed25519.NewKeyFromSeed(seed)

type customPayloadStruct struct {
Name string `json:"name"`
ExpiresAt time.Time `json:"expiresAt"`
}

func main() {

app := fiber.New()

// Login route
app.Post("/login", login)

// Unauthenticated route
app.Get("/", accessible)

// Paseto Middleware with local (encrypted) token
apiGroup := app.Group("api", pasetoware.New(pasetoware.Config{
TokenPrefix: "Bearer",
PrivateKey: privateKey,
PublicKey: privateKey.Public(),
}))

// Restricted Routes
apiGroup.Get("/restricted", restricted)

err := app.Listen(":8088")
if err != nil {
return
}
}

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

// Throws Unauthorized error
if user != "john" || pass != "doe" {
return c.SendStatus(fiber.StatusUnauthorized)
}

// Create token and encrypt it
encryptedToken, err := pasetoware.CreateToken(privateKey, user, 12*time.Hour, pasetoware.PurposePublic)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}

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

func accessible(c *fiber.Ctx) error {
return c.SendString("Accessible")
}

func restricted(c *fiber.Ctx) error {
payload := c.Locals(pasetoware.DefaultContextKey).(string)
return c.SendString("Welcome " + payload)
}

测试它

使用用户名和密码登录以检索令牌。

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

响应

{
"token": "v2.public.eyJhdWQiOiJnb2ZpYmVyLmdvcGhlcnMiLCJkYXRhIjoiam9obiIsImV4cCI6IjIwMjMtMDctMTNUMDg6NDk6MzctMDM6MDAiLCJpYXQiOiIyMDIzLTA3LTEyVDIwOjQ5OjM3LTAzOjAwIiwianRpIjoiMjIzYjM0MjQtNWNkZS00NDFhLWJiZWEtZjBjYWFhYTdiYWFlIiwibmJmIjoiMjAyMy0wNy0xMlQyMDo0OTozNy0wMzowMCIsInN1YiI6InVzZXItdG9rZW4ifWiqK_yg0eJbIs2hnup4NuBYg7v4lxh33zEhEljsH7QUaZXAdtbCPK7cN-NSfSxrw68owwgo-dOlPrD7lc5M_AU.bnVsbA"
}

在 Authorization 请求头中使用令牌请求受限资源。

curl localhost:8088/api/restricted -H "Authorization: Bearer v2.public.eyJhdWQiOiJnb2ZpYmVyLmdvcGhlcnMiLCJkYXRhIjoiam9obiIsImV4cCI6IjIwMjMtMDctMTNUMDg6NDk6MzctMDM6MDAiLCJpYXQiOiIyMDIzLTA3LTEyVDIwOjQ5OjM3LTAzOjAwIiwianRpIjoiMjIzYjM0MjQtNWNkZS00NDFhLWJiZWEtZjBjYWFhYTdiYWFlIiwibmJmIjoiMjAyMy0wNy0xMlQyMDo0OTozNy0wMzowMCIsInN1YiI6InVzZXItdG9rZW4ifWiqK_yg0eJbIs2hnup4NuBYg7v4lxh33zEhEljsH7QUaZXAdtbCPK7cN-NSfSxrw68owwgo-dOlPrD7lc5M_AU.bnVsbA"

响应

Welcome John Doe