发布于 2026-01-06 0 阅读
0

如何在 Golang DEV 中创建和验证 JWT 和 PASETO 令牌?Mux 呈现的全球展示挑战赛:展示你的项目!

如何在 Golang 中创建和验证 JWT 和 PASETO 令牌

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

大家好!

上一讲中,我们学习了基于令牌的身份验证,以及为什么 PASETO 在安全实践方面比 JWT 更好。

今天我们将学习如何在 Golang 中实现它们,以了解为什么 PASETO 比 JWT 更容易、更简单地实现。

以下是:

声明令牌生成器接口

好了,我们开始吧!

首先,我将创建一个名为 的新包。然后在该包内token创建一个新文件。maker.go

我们的想法是声明一个通用token.Maker接口来管理令牌的创建和验证。之后,我们将编写一个实现该接口的结构JWTMakerPasetoMaker。这样,我们就可以随时轻松地在不同类型的令牌生成器之间切换。

因此,该接口将包含 2 个方法:



type Maker interface {
    CreateToken(username string, duration time.Duration) (string, error)
    VerifyToken(token string) (*Payload, error)
}


Enter fullscreen mode Exit fullscreen mode

CreateToken()方法接受一个username字符串和一个有效期限duration作为输入。它返回一个已签名的令牌字符串或一个错误。简而言之,此方法将为指定的用户名和有效期限创建并签名一个新令牌。

第二个方法是VerifyToken()`getToken()`,它接受一个令牌字符串作为输入,并返回一个Payload对象或一个错误。我们Playload稍后会声明这个结构体。此VerifyToken()方法的作用是检查输入的令牌是否有效。如果有效,该方法将返回存储在令牌主体中的有效负载数据。

声明令牌有效载荷结构

好的,现在我们创建一个新payload.go文件,并Payload在其中定义结构体。这个结构体将包含令牌的有效载荷数据。

最重要的字段是Username,用于识别代币所有者。

然后IssuedAt添加一个字段,用于记录令牌何时创建。

在使用基于令牌的身份验证时,确保每个访问令牌的有效期较短至关重要。因此,我们需要一个ExpiredAt字段来存储令牌的过期时间。



type Payload struct {
    ID        uuid.UUID `json:"id"`
    Username  string    `json:"username"`
    IssuedAt  time.Time `json:"issued_at"`
    ExpiredAt time.Time `json:"expired_at"`
}


Enter fullscreen mode Exit fullscreen mode

通常情况下,这三个字段就足够了。但是,如果我们想在特定令牌泄露时使其失效,则需要添加一个ID字段来唯一标识每个令牌。

这里我为该字段使用了 UUID 类型。该类型定义在google/uuid包中,因此我们需要运行go get命令将其下载并添加到项目中。



go get github.com/google/uuid


Enter fullscreen mode Exit fullscreen mode

接下来,我将定义一个函数,NewPayload()该函数接受一个 `a`username和一个 `b`duration作为输入参数,并返回一个Payload对象或一个错误。此函数将创建一个包含特定用户名和有效期的新令牌有效负载。



func NewPayload(username string, duration time.Duration) (*Payload, error) {
    tokenID, err := uuid.NewRandom()
    if err != nil {
        return nil, err
    }

    payload := &Payload{
        ID:        tokenID,
        Username:  username,
        IssuedAt:  time.Now(),
        ExpiredAt: time.Now().Add(duration),
    }
    return payload, nil
}


Enter fullscreen mode Exit fullscreen mode

首先,我们需要调用函数uuid.NewRandom()来生成唯一的令牌 ID。如果发生错误,我们只需返回空有效负载和错误本身即可。

否则,我们创建有效载荷,其中ID是生成的随机标记UUIDUsername是输入usernameIssuedAttime.Now()ExpiredAttime.Now().Add(duration)

然后我们只需返回这个有效负载对象和一个nil错误信息。就完成了!

实现 JWT 生成器

现在我们要实现一个功能JWTMaker。我们需要一个 Golang 的 JWT 包,所以让我们打开浏览器并搜索jwt golang

可能有很多不同的软件包,但我认为这个是最受欢迎的:https://github.com/dgrijalva/jwt-go。所以我们复制它的 URL,然后go get在终端中运行命令来安装这个软件包:



go get github.com/dgrijalva/jwt-go


Enter fullscreen mode Exit fullscreen mode

好了,软件包已安装完毕。现在我们回到 Visual Studio Code。

我将jwt_maker.gotoken包内创建一个新文件。然后声明一个新的JWTMaker结构体类型。这个结构体是一个 JSON Web Token 生成器,它实现了token.Maker相应的接口。

在本教程中,我将使用对称密钥算法对令牌进行签名,因此该结构体将有一个字段来存储密钥。



type JWTMaker struct {
    secretKey string
}


Enter fullscreen mode Exit fullscreen mode

接下来,我们添加一个函数NewJWTMaker(),该函数接受一个secretKey字符串作为输入,并返回一个token.Maker接口或一个对象error作为输出。

通过返回接口,我们可以确保我们的对象JWTMaker必须实现该token.Maker接口。稍后我们将看到 Go 编译器是如何检查这一点的。

虽然我们将要使用的算法并不要求密钥长度,但为了提高安全性,最好还是确保密钥不要太短。因此,我将声明一个常量minSecretKeySize = 32字符。



const minSecretKeySize = 32

func NewJWTMaker(secretKey string) (Maker, error) {
    if len(secretKey) < minSecretKeySize {
        return nil, fmt.Errorf("invalid key size: must be at least %d characters", minSecretKeySize)
    }
    return &JWTMaker{secretKey}, nil
}


Enter fullscreen mode Exit fullscreen mode

然后,在这个函数内部,我们会检查密钥的长度是否小于minSecretKeySize或等于 32 个字符。如果小于或等于 32 个字符,则返回一个nil对象并报错,提示密钥必须至少包含 32 个字符。

否则,我们将返回一个JWTMaker包含输入的新对象secretKey和一个nil错误。

替代文字

现在您可以看到这里有一条红线,因为JWTMaker我们创建的对象没有实现该token.Maker接口所需的方法,而该接口是此函数的返回类型。

所以为了解决这个问题,我们需要在这个结构体中添加CreateToken()and方法。VerifyToken()

让我们从界面复制它们,然后粘贴到这里。接下来,在每个方法前面token.Maker添加接收器。JWTMaker



func (maker *JWTMaker) CreateToken(username string, duration time.Duration) (string, error) {}

func (maker *JWTMaker) VerifyToken(token string) (*Payload, error) {}


Enter fullscreen mode Exit fullscreen mode

好了,现在红线消失了!让我们来实现这个CreateToken()方法吧!

实现 JWT CreateToken 方法

首先,我们通过调用创建一个新的令牌有效负载NewPayload(),并传入输入username和有效值duration

jwtToken如果 error 不为 nil,则返回一个空的 token 字符串和错误信息。否则,调用jwt.NewWithClaims()jwt-go 包中的函数创建一个新的 token 。

此函数需要 2 个输入参数:

  • 首先是签名方法(或算法)。我将HS256在本例中使用这种方法。
  • 然后是声明,这实际上是我们创建的有效载荷。


func (maker *JWTMaker) CreateToken(username string, duration time.Duration) (string, error) {
    payload, err := NewPayload(username, duration)
    if err != nil {
        return "", err
    }

    jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
    return jwtToken.SignedString([]byte(maker.secretKey))
}


Enter fullscreen mode Exit fullscreen mode

最后,为了生成标记字符串,我们调用,并将转换为切片后jwtToken.SignedString()传入secretKey[]byte

替代文字

这里出现了一个错误,因为我们的Payload结构体没有实现该jwt.Claims接口。它缺少一个名为 `.` 的方法Valid()

jwt-go 包需要这个方法来检查令牌负载是否有效。所以我们来payload.go添加这个方法。

该方法的签名非常简单。它不接受任何输入参数,仅在令牌无效时返回错误。您可以在 jwt-go 包的实现中轻松找到此方法。



var ErrExpiredToken = errors.New("token has expired")

func (payload *Payload) Valid() error {
    if time.Now().After(payload.ExpiredAt) {
        return ErrExpiredToken
    }
    return nil
}


Enter fullscreen mode Exit fullscreen mode

最简单但也是最重要的一点是,我们必须检查令牌的过期时间。

如果time.Now()在之后payload.ExpiredAt,则表示令牌已过期。因此,我们只需返回一个新的错误信息:令牌已过期。

我们应该将此错误声明为公共常量:ErrExpiredToken,以便我们可以从外部检查错误类型。

如果令牌未过期,则直接返回nil。这样就完成了!Valid 函数编写完毕。

现在回到我们的jwt_maker.go文件,我们可以看到有效载荷对象上的红线消失了。

导入jwt-go软件包后,我们应该go mod tidy在终端中运行命令将其添加到go.mod文件中。



module github.com/techschool/simplebank

go 1.15

require (
    github.com/dgrijalva/jwt-go v3.2.0+incompatible
    github.com/gin-gonic/gin v1.6.3
    github.com/go-playground/validator/v10 v10.4.1
    github.com/golang/mock v1.4.4
    github.com/google/uuid v1.1.4
    github.com/lib/pq v1.9.0
    github.com/o1egl/paseto v1.0.0
    github.com/spf13/viper v1.7.1
    github.com/stretchr/testify v1.6.1
    golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
)


Enter fullscreen mode Exit fullscreen mode

我目前使用的 jwt-go 版本是 [版本号] 3.2.0。将来您可能会使用更新的版本,例如 [4.0版本号],届时函数和接口签名可能会有所不同。但是,基本思路应该类似。

好了,这个CreateToken()方法就完成了。接下来我们来看下一个VerifyToken()方法。

实现 JWT VerifyToken 方法

这会稍微复杂一些。首先,我们需要调用解析令牌的函数jwt.ParseWithClaims,并传入输入token字符串、一个空Payload对象和一个键函数。

什么是密钥函数?简单来说,它是一个接收已解析但未经验证的令牌的函数。您应该验证其头部信息,以确保其签名算法与您通常用于签署令牌的算法相匹配。

如果匹配成功,则返回密钥,以便 jwt-go 可以使用它来验证令牌。正如我在上一讲中解释的那样,这一步对于防止简单的攻击机制至关重要

好的,我要复制这个函数签名,然后粘贴到这里。我们把这个输入参数的名称设为 `<name>` token,类型设为 `<type>` jwt.Token。然后我们只需将 `keyFunc` 传递给ParseWithClaims()调用即可。



func (maker *JWTMaker) VerifyToken(token string) (*Payload, error) {
    keyFunc := func(token *jwt.Token) (interface{}, error) {
        _, ok := token.Method.(*jwt.SigningMethodHMAC)
        if !ok {
            return nil, ErrInvalidToken
        }
        return []byte(maker.secretKey), nil
    }

    jwtToken, err := jwt.ParseWithClaims(token, &Payload{}, keyFunc)
    ...
}


Enter fullscreen mode Exit fullscreen mode

在密钥函数中,我们可以通过token.Method字段获取其签名算法。请注意,它的类型是 `A` SigningMethod,也就是一个 `A` interface。因此,我们需要尝试将其转换为特定的实现。

在我们的例子中,我们将其转换为,SigningMethodHMAC因为我们使用的是HS256,它是结构体的实例SigningMethodHMAC

这种转换可能成功,也可能失败。如果转换失败,则意味着该代币的算法与我们的签名算法不匹配,因此它显然是一个无效代币。

我们需要返回一个nil带有 . 的键ErrInvalidToken。我将在payload.go文件中声明这个新的错误,位置与 . 相同ErrExpiredToken。它们是我们的函数将返回的不同类型的错误VerifyToken()



var (
    ErrInvalidToken = errors.New("token is invalid")
    ErrExpiredToken = errors.New("token has expired")
)


Enter fullscreen mode Exit fullscreen mode

好的,回到正题JWTMaker。如果转换成功,则表示算法匹配。我们可以直接返回用于对令牌进行签名的密钥(在将其转换为[]byte切片之后),并返回一个空值错误。

好了,现在密钥函数已经准备就绪。我们继续ParseWithClaims调用这个函数。如果它返回非空错误,则可能存在两种情况:令牌无效或已过期。

但现在,当我们想要区分这两种情况时,事情就变得更加复杂了。如果我们跟踪 jwt-go 包的实现,就会发现它token.Claims.Valid()在底层会自动调用函数。

替代文字

在我们的函数实现中,我们返回了ErrExpiredToken错误。然而,jwt-go 却将这个原始错误隐藏在了它自己的ValidationError对象中。

因此,为了确定真正的错误类型,我们必须将函数返回的错误转换ParseWithClaims()jwt.ValidationError



func (maker *JWTMaker) VerifyToken(token string) (*Payload, error) {
    ...

    jwtToken, err := jwt.ParseWithClaims(token, &Payload{}, keyFunc)
    if err != nil {
        verr, ok := err.(*jwt.ValidationError)
        if ok && errors.Is(verr.Inner, ErrExpiredToken) {
            return nil, ErrExpiredToken
        }
        return nil, ErrInvalidToken
    }

    ...
}


Enter fullscreen mode Exit fullscreen mode

这里我将转换后的错误值赋给一个verr变量。如果转换成功,我们使用该errors.Is()函数来检查它是否verr.Inner真的是目标值ErrExpiredToken

如果是,我们就返回nil有效载荷和ErrExpiredToken。否则,我们就返回 nil 和ErrInvalidToken

如果一切顺利,令牌已成功解析和验证,我们将尝试将其转换jwtToken.Claims为 Payload 对象以获取其有效负载数据。



func (maker *JWTMaker) VerifyToken(token string) (*Payload, error) {
    keyFunc := func(token *jwt.Token) (interface{}, error) {
        _, ok := token.Method.(*jwt.SigningMethodHMAC)
        if !ok {
            return nil, ErrInvalidToken
        }
        return []byte(maker.secretKey), nil
    }

    jwtToken, err := jwt.ParseWithClaims(token, &Payload{}, keyFunc)
    if err != nil {
        verr, ok := err.(*jwt.ValidationError)
        if ok && errors.Is(verr.Inner, ErrExpiredToken) {
            return nil, ErrExpiredToken
        }
        return nil, ErrInvalidToken
    }

    payload, ok := jwtToken.Claims.(*Payload)
    if !ok {
        return nil, ErrInvalidToken
    }

    return payload, nil
    }



Enter fullscreen mode Exit fullscreen mode

如果失败,则直接返回nilErrInvalidToken否则,返回有效负载对象和一个nil错误。

好了!项目JWTMaker完成了。现在让我们来编写一些单元测试吧!

测试 JWT 生成器

我将在包jwt_maker_test.go内创建一个新文件token。然后,我们来添加一个TestJWTMaker()testing.T对象作为输入的新函数。

首先,我们需要调用该NewJWTMaker()函数创建一个新的生成器,并传入一个长度为 32 个字符的随机密钥。这里不需要返回任何错误。

接下来,我们使用usernameutil.RandomOwner()函数生成一个,假设有效的令牌duration将是1 minute

我们还声明了两个变量,以便稍后比较结果:

  • 时间issuedAt应该是time.Now()
  • 我们将该时间加到durationissuedAt时间上,得到expiredAt令牌的时间。


func TestJWTMaker(t *testing.T) {
    maker, err := NewJWTMaker(util.RandomString(32))
    require.NoError(t, err)

    username := util.RandomOwner()
    duration := time.Minute

    issuedAt := time.Now()
    expiredAt := issuedAt.Add(duration)

    token, err := maker.CreateToken(username, duration)
    require.NoError(t, err)
    require.NotEmpty(t, token)

    payload, err := maker.VerifyToken(token)
    require.NoError(t, err)
    require.NotEmpty(t, token)

    require.NotZero(t, payload.ID)
    require.Equal(t, username, payload.Username)
    require.WithinDuration(t, issuedAt, payload.IssuedAt, time.Second)
    require.WithinDuration(t, expiredAt, payload.ExpiredAt, time.Second)
}


Enter fullscreen mode Exit fullscreen mode

好的,现在我们通过调用函数生成令牌maker.CreatToken,并传入参数usernameduration要求不出现任何错误,并且要求输出令牌不能为空。

接下来,我们调用函数maker.VerifyToken来验证令牌是否有效,并获取其有效负载数据。我们要求没有错误,并且有效负载对象不能为空。

接下来我们需要检查有效负载对象的所有字段。

  • 首先,该值payload.ID不能为零。
  • 那么它payload.Username应该等于输入username
  • 我们会将实际结果与之前预估的节省时间require.WithinDuration进行比较。两者之间的差异不应超过 1 秒。payload.IssuedAtissuedAt
  • 同样地,我们用同样的方法将payload.ExpiredAt场强与预期时间进行比较。expiredAt

完成了!让我们运行这个单元测试!

替代文字

通过了。太好了!这就是我们测试正常情况的方法。

现在我们再添加一个测试,来检查 JWT 令牌过期的情况。

与之前类似,我们首先需要创建一个新的令牌JWTMaker。然后我们将通过调用来创建一个过期的令牌maker.CreateToken(),传入一个随机数username和一个值negative duration



func TestExpiredJWTToken(t *testing.T) {
    maker, err := NewJWTMaker(util.RandomString(32))
    require.NoError(t, err)

    token, err := maker.CreateToken(util.RandomOwner(), -time.Minute)
    require.NoError(t, err)
    require.NotEmpty(t, token)

    payload, err := maker.VerifyToken(token)
    require.Error(t, err)
    require.EqualError(t, err, ErrExpiredToken.Error())
    require.Nil(t, payload)
}


Enter fullscreen mode Exit fullscreen mode

我们要求不得返回任何错误,且令牌不能为空。现在我们将验证此输出令牌。

这次,我们预期会返回一个错误。更具体地说,它应该是ErrExpiredToken……。最后,输出有效负载应该是nil……。

好的,我们来运行测试!

替代文字

通过了。太好了!

我们要编写的最后一个测试是检查无效令牌的情况,其中None使用了算法标头。这是一种众所周知的攻击技术,我在上一节课中已经讲过。

首先,我将创建一个新的令牌,payload使用随机数usernameduration1 分钟的有效期。要求无错误。然后,让我们通过调用并传入新令牌来创建一个新jwt.NewWithClaims()jwt.SigningMethodNone令牌payload

现在我们需要使用该SignedString()方法对令牌进行签名。但是我们不能随意使用任何随机密钥,因为 jwt-go 库完全禁止使用该None算法对令牌进行签名。

只有当传入这个特殊常量jwt.UnsafeAllowNoneSignatureType作为密钥时,才能将其用于测试。

如果你仔细阅读这个值的实现说明,你会发现通常情况下,除非输入的键是这个特殊常量,否则 `None` 符号是不允许使用的。这基本上意味着你清楚自己在做什么。请确保仅在测试环境中使用它,不要用于生产环境。



func TestInvalidJWTTokenAlgNone(t *testing.T) {
    payload, err := NewPayload(util.RandomOwner(), time.Minute)
    require.NoError(t, err)

    jwtToken := jwt.NewWithClaims(jwt.SigningMethodNone, payload)
    token, err := jwtToken.SignedString(jwt.UnsafeAllowNoneSignatureType)
    require.NoError(t, err)

    maker, err := NewJWTMaker(util.RandomString(32))
    require.NoError(t, err)

    payload, err = maker.VerifyToken(token)
    require.Error(t, err)
    require.EqualError(t, err, ErrInvalidToken.Error())
    require.Nil(t, payload)
}


Enter fullscreen mode Exit fullscreen mode

好了,我们回到代码。我们需要JWTMaker像其他测试一样创建一个新的令牌。现在我们调用函数maker.VerifyToken()来验证上面签名的令牌。

这次,该函数也应该返回一个错误,并且错误值应该等于ErrInvalidToken。输出有效负载也应该是nil

好了,现在让我们运行测试!

替代文字

通过了!太棒了!

现在您知道如何在 Go 语言中实现和测试 JWT 了。

虽然我认为 jwt-go 包在防止安全错误方面做得相当不错,但它仍然比必要的要复杂和难以使用,尤其是在令牌验证部分。

实现 PASETO Maker

现在我将向您展示如何使用 PASETO 实现相同的令牌生成器接口。这比 JWT 更简单、更简洁。

好的,我们打开浏览器搜索paseto golang。打开它的 GitHub 页面并复制 URL:https://github.com/o1egl/paseto。然后go get使用此 URL 运行命令下载软件包:



go get github.com/o1egl/paseto


Enter fullscreen mode Exit fullscreen mode

现在回到我们的项目,我将在文件夹paseto_maker.go内创建一个新文件token

类似于我们对 JWT 所做的,让我们声明一个类型PasetoMaker结构,它将实现相同的token.Maker接口,但使用 PASETO 而不是 JWT。

我们目前将使用最新版本的 PASETO,即版本 2。因此,PasetoMaker结构体将有一个paseto类型为 的字段paseto.V2



type PasetoMaker struct {
    paseto       *paseto.V2
    symmetricKey []byte
}


Enter fullscreen mode Exit fullscreen mode

由于我只想在本地将此令牌用于我们的银行 API,我们将使用对称加密来加密令牌有效负载。因此,我们需要一个字段来存储它symmetricKey

好的,现在我们来添加一个函数NewPasetoMaker(),该函数接受一个symmetricKey字符串作为输入,并返回一个token.Maker接口或对象error。该函数将创建一个新的PasetoMaker实例。

Paseto 版本 2 使用Chacha20 Poly1305算法对有效载荷进行加密。因此,我们需要检查对称密钥的长度,以确保其符合算法要求。



import (
    "github.com/aead/chacha20poly1305"
    "github.com/o1egl/paseto"
)

func NewPasetoMaker(symmetricKey string) (Maker, error) {
    if len(symmetricKey) != chacha20poly1305.KeySize {
        return nil, fmt.Errorf("invalid key size: must be exactly %d characters", chacha20poly1305.KeySize)
    }

    maker := &PasetoMaker{
        paseto:       paseto.NewV2(),
        symmetricKey: []byte(symmetricKey),
    }

    return maker, nil
}


Enter fullscreen mode Exit fullscreen mode

如果键长不正确,则返回一个 nil 对象,并报错提示“键长无效”。键长必须正好是指定的字符数。

否则,我们只需创建一个新的 PasetoMaker 对象,其中包含转换为切片的paseto.NewV2()输入symmetricKey[]byte

然后我们返回该maker对象和一个nil错误。

同样,这里我们在 maker 对象下方看到一条红线,因为它尚未实现该token.Maker接口。所以,让我们对它执行与之前相同的操作JWTMaker

我将复制令牌生成器接口的这两个必需方法,并PasetoMaker在它们前面添加接收器。



func (maker *PasetoMaker) CreateToken(username string, duration time.Duration) (string, error) {}

func (maker *PasetoMaker) VerifyToken(token string) (*Payload, error) {}


Enter fullscreen mode Exit fullscreen mode

好了,现在红线消失了。让我们来实现这个CreateToken()方法。

实现 PASETO CreateToken 方法

与之前类似,我们首先需要使用payload输入username和创建一个新的对象duration。如果错误不是nil,我们将返回一个空字符串和错误信息给调用者。

否则,我们返回maker.paseto.Encrypt(),并将参数maker.symmetricKeypayload对象传递给它。最后一个参数是可选的页脚,我们不需要它,所以我把它放在nil这里。



func (maker *PasetoMaker) CreateToken(username string, duration time.Duration) (string, error) {
    payload, err := NewPayload(username, duration)
    if err != nil {
        return "", err
    }

    return maker.paseto.Encrypt(maker.symmetricKey, payload, nil)
}



Enter fullscreen mode Exit fullscreen mode

就是这样!很简短,对吧?

如果我们跟踪该函数的实现过程Encrypt(),就会发现它使用的是Chacha Poly密码算法。

替代文字

函数内部newCipher()还会检查输入,key size确保它等于某个值32 bytes

实现 PASETO VerifyToken 方法

好了,现在我们回到代码,实现这个VerifyToken()方法。非常简单!

我们只需要声明一个空payload对象来存储解密后的数据。然后调用该对象,maker.paseto.Decrypt()传入输入token、值symmetricKey、值payloadnil页脚。



func (maker *PasetoMaker) VerifyToken(token string) (*Payload, error) {
    payload := &Payload{}

    err := maker.paseto.Decrypt(token, maker.symmetricKey, payload, nil)
    if err != nil {
        return nil, ErrInvalidToken
    }

    err = payload.Valid()
    if err != nil {
        return nil, err
    }

    return payload, nil
}


Enter fullscreen mode Exit fullscreen mode

如果error不是nil,则返回nil有效负载ErrInvalidToken。否则,我们将通过调用来检查令牌是否有效payload.Valid()

如果出现错误,我们只返回nil有效负载和对象error本身。否则,我们返回对象payload和一个nil错误信息。

就是这样!非常简洁,比JWT简单多了,对吧?

测试 PASETO Maker

好了,现在我们来编写一些单元测试!

我将在包paseto_maker_test.go内创建一个新文件token。实际上,这个测试几乎和我们之前为 JWT 编写的测试完全相同,所以我直接把它复制到这里。

将其名称改为。然后在这里,我们TestPasetoMaker用代替NewJWTMaker()NewPasetoMaker()



func TestPasetoMaker(t *testing.T) {
    maker, err := NewPasetoMaker(util.RandomString(32))
    require.NoError(t, err)

    username := util.RandomOwner()
    duration := time.Minute

    issuedAt := time.Now()
    expiredAt := issuedAt.Add(duration)

    token, err := maker.CreateToken(username, duration)
    require.NoError(t, err)
    require.NotEmpty(t, token)

    payload, err := maker.VerifyToken(token)
    require.NoError(t, err)
    require.NotEmpty(t, token)

    require.NotZero(t, payload.ID)
    require.Equal(t, username, payload.Username)
    require.WithinDuration(t, issuedAt, payload.IssuedAt, time.Second)
    require.WithinDuration(t, expiredAt, payload.ExpiredAt, time.Second)
}


Enter fullscreen mode Exit fullscreen mode

我们无需更改其他任何内容,因为PasetoMaker它实现了与相同的token.Maker接口JWTMaker

让我们运行测试!

替代文字

通过了!

现在让我们复制过期令牌情况的测试!将其名称更改为TestExpiredPasetoToken,并将此调用更新为NewPasetoMaker()



func TestExpiredPasetoToken(t *testing.T) {
    maker, err := NewPasetoMaker(util.RandomString(32))
    require.NoError(t, err)

    token, err := maker.CreateToken(util.RandomOwner(), -time.Minute)
    require.NoError(t, err)
    require.NotEmpty(t, token)

    payload, err := maker.VerifyToken(token)
    require.Error(t, err)
    require.EqualError(t, err, ErrExpiredToken.Error())
    require.Nil(t, payload)
}


Enter fullscreen mode Exit fullscreen mode

然后运行测试!

替代文字

也通过了。太好了!

我们不需要最后一个测试,因为NonePASETO 中根本不存在这种算法。如果你愿意,可以编写另一个测试来检查无效标记的情况。我把它留给你作为练习。

本次讲座到此结束。我们学习了如何使用 Go 语言实现 JWT 和 PASETO 来创建和验证访问令牌。

在下一篇文章中,我将向您展示如何在登录 API 中使用它们,用户提供用户名和密码,如果提供的凭据正确,服务器将返回访问令牌。

非常感谢您的阅读,我们下节课再见!


如果您喜欢这篇文章,请订阅我们的YouTube 频道,并在TwitterFacebook上关注我们,以便将来获取更多教程。


如果你想加入我在Voodoo的优秀团队,请点击此处查看我们的招聘信息。可远程办公,也可在巴黎/阿姆斯特丹/伦敦/柏林/巴塞罗那现场办公,公司提供签证担保。

文章来源:https://dev.to/techschoolguru/how-to-create-and-verify-jwt-paseto-token-in-golang-1l5j