在Golang中创建和验证一个JWT RSA令牌(附实例)

1,290 阅读1分钟

在这个例子中,我们将使用RSA RS256私钥创建一个JWT令牌,并用公钥对其进行验证。RSA是一种非对称的签名方法,在创建和验证时都使用不同的密钥。如果令牌的创建者(服务器应用程序)和用户(客户端应用程序)都被允许验证它,就可以使用这种方法。令牌创建者会知道私钥和公钥,而用户只知道公钥。

首先运行下面的命令来创建私人和公共RSA密钥:

.PHONY: cert
cert:
	openssl genrsa -out cert/id_rsa 4096
	openssl rsa -in cert/id_rsa -pubout -out cert/id_rsa.pub

main.go

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"time"

	"github.com/you/client/internal/pkg/token"
)

func main() {
	prvKey, err := ioutil.ReadFile("cert/id_rsa")
	if err != nil {
		log.Fatalln(err)
	}
	pubKey, err := ioutil.ReadFile("cert/id_rsa.pub")
	if err != nil {
		log.Fatalln(err)
	}

	jwtToken := token.NewJWT(prvKey, pubKey)

	// 1. Create a new JWT token.
	tok, err := jwtToken.Create(time.Hour, "Can be anything")
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println("TOKEN:", tok)

	// 2. Validate an existing JWT token.
	content, err := jwtToken.Validate(tok)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println("CONTENT:", content)
}

token.go

package token

import (
	"fmt"
	"time"

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

type JWT struct {
	privateKey []byte
	publicKey  []byte
}

func NewJWT(privateKey []byte, publicKey []byte) JWT {
	return JWT{
		privateKey: privateKey,
		publicKey:  publicKey,
	}
}

func (j JWT) Create(ttl time.Duration, content interface{}) (string, error) {
	key, err := jwt.ParseRSAPrivateKeyFromPEM(j.privateKey)
	if err != nil {
		return "", fmt.Errorf("create: parse key: %w", err)
	}

	now := time.Now().UTC()

	claims := make(jwt.MapClaims)
	claims["dat"] = content             // Our custom data.
	claims["exp"] = now.Add(ttl).Unix() // The expiration time after which the token must be disregarded.
	claims["iat"] = now.Unix()          // The time at which the token was issued.
	claims["nbf"] = now.Unix()          // The time before which the token must be disregarded.

	token, err := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
	if err != nil {
		return "", fmt.Errorf("create: sign token: %w", err)
	}

	return token, nil
}

func (j JWT) Validate(token string) (interface{}, error) {
	key, err := jwt.ParseRSAPublicKeyFromPEM(j.publicKey)
	if err != nil {
		return "", fmt.Errorf("validate: parse key: %w", err)
	}

	tok, err := jwt.Parse(token, func(jwtToken *jwt.Token) (interface{}, error) {
		if _, ok := jwtToken.Method.(*jwt.SigningMethodRSA); !ok {
			return nil, fmt.Errorf("unexpected method: %s", jwtToken.Header["alg"])
		}

		return key, nil
	})
	if err != nil {
		return nil, fmt.Errorf("validate: %w", err)
	}

	claims, ok := tok.Claims.(jwt.MapClaims)
	if !ok || !tok.Valid {
		return nil, fmt.Errorf("validate: invalid")
	}

	return claims["dat"], nil
}

测试

使用 jwt.io 对令牌和 JSON Web 令牌 (JWT) 进行解码,了解有关 JWT 的详细信息。

$ go run -race main.go

TOKEN: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXQiOiJDYW4gYmUgYW55dGhpbmciLCJleHAiOjE2MDQyNTI5NzUsImlhdCI6MTYwNDI0OTM3NSwibmJmIjoxNjA0MjQ5Mzc1fQ.Ig-odzhYvhBLVvbs7y2zELFC1qgLCp4EC-XrDmcxhxHxIwS1u3i3gQ8FnzI7CKa495CFgZtwjk_SJhUGyCQQmVjpMpGYrZiJJ_4QiIlC8b0bFrq65SZOqumMzSE9pUH0V-pWiIzkqWni6PX9KLS7YJvr8o7l1dJ772d5nWw8wZDVJn76PHJo7SEtInA3-l-oxDvQ2rqtefo5enkDM_2Yg77h3542KwGFZVig8B-bzl6kO8w391gXJa8GdfIsbLsXFnI1-LdWZzjXSnD3wsUV8PsBJ0AkCYccwV7i4Sk4d56XkgTcb5IHixcwm64RXkEWmxw_RLZlMz4Fa6mSSB3nTbJnYFGxV8t35KKQjrsTdaRuVfGaxw_i54JAktpJxoRioR846f1o_OsvyrHY1cDi8kPEVinuW_UiRBLD26dOEvBbreM0bQaPn9K3_a6gBwtQX0xdaQsSha4dvvPZ-krzZr3TWzkewQRCRaZqJeL0pdiPV_l95R3HUCQTrznLzKu-QlwxBELrC92NK6ex13XuovBFRHfZDnTaXFc0JcSmXLRAAL7PREcLiAZCTz5b0oVP7K_vIjf2LhqYTPgUxUYm4HZySBxPEtu5QiVZA807nEBXpTWZ4DZAlyPYXokzV1jFfDBRtYRwxA6CqW7SWUwOPFal0IxvjlV4sP5SJwqGawM

CONTENT: Can be anything