Создание и анализ JWT в Go

В этой статье мы покажем вам, как создавать и анализировать токены JWT, извлекая из них информацию на прекрасном языке Go, используя пакет go-jwt.

Данная статья предполагает следующее:

  • Вы умеете программировать на Go .
  • Вы знаете, что такое JWT и как они работают.

Добавление пакета go-jwt

Мы будем использовать пакет go-jwt для создания и анализа токенов JWT.

go get -u github.com/golang-jwt/jwt/v4

На момент написания этой статьи последняя версия этого пакета — v4.

Создание токена JWT

При создании токена JWT нам нужен секретный ключ, поэтому начните с определения нового ключа.

package main

const key = "my secure jwt key"

func main() {
}
main.go

Для создания новых токенов мы используем jwt.NewWithClaims функцию.

package main

import (
	"fmt"
	"log"
	"github.com/golang-jwt/jwt/v4"
)

const key = "my secure jwt key"

func main() {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{}) 
    
    jwtToken, err := token.SignedString([]byte(key)) 
    if err != nil {
    	log.Fatal(err)
    }
    
    fmt.Printf("JWT Token: %s\n", jwtToken)
}
main.go

Позвольте рассказать вам об этом:

  • NewWithClaims принимает два параметра: метод подписи и утверждения . Утверждения — это фактические данные, которые будет содержать токен JWT.
  • jwt.NewWithClaims не создает новый токен, вам нужно вызвать SignedString функцию, передав ей секретный ключ , чтобы получить фактический токен JWT.
  • jwt.RegisteredClaims — это общие/стандартные утверждения JWT, которые обычно присутствуют в полезной нагрузке токена JWT, например iat(время выпуска токена), exp(время истечения срока действия токена) и многие другие.

Создание токена с данными

Код до сих пор создает токен JWT, но не содержит никаких данных, предоставленных пользователем. Это просто: вместо предоставления jwt.RegisteredClaims в качестве полезной нагрузки вы встраиваете jwt.RegisteredClaims другую структуру с дополнительной информацией, назовем ее UserClaim.

type UserClaim struct {
	jwt.RegisteredClaims
	ID    int 
	Email string
	Name  string
}

Используйте UserClaim как второй аргумент функции jwt.NewWithClaims.

token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{
	RegisteredClaims: jwt.RegisteredClaims{},
	ID: "1",
	Email: "email@email.com",
	Name: "First Last",
})

Мы собираемся провести рефакторинг процесса создания токена JWT в отдельную функцию под названием CreateJWTToken, ниже приведен полный код.

package main

import (
	"fmt"
	"log"
	"github.com/golang-jwt/jwt/v4"
)

const key = "my secure jwt key"

// Data that will be in the token
type UserClaim struct {
	jwt.RegisteredClaims
	ID    int 
	Email string
	Name  string
}

func main() {
	jwtToken, err := CreateJWTToken(1, "email@email.com", "First Last")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("JWT Token: %s\n", jwtToken)
}

// 👇
func CreateJWTToken(id int, email string, name string) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{
		RegisteredClaims: jwt.RegisteredClaims{},
		ID: id,
		Email: email,
		Name: name,
	})

	// Create the actual JWT token
	signedString, err := token.SignedString([]byte(key))

	if err != nil {
		return "", fmt.Errorf("error creating signed string: %v", err)
	}

	return signedString, nil
}
main.go

Анализ токена JWT

Анализ токенов JWT и извлечение из них данных можно выполнить с помощью функции jwt.ParseWithClaims(есть еще одна функция jwt.Parse, которая позволит вам просто проанализировать и проверить, действителен ли токен).

var jwtToken string // a token generated from previous code
var userClaim UserClaim

token, err := jwt.ParseWithClaims(jwtToken, &userClaim, func(token *jwt.Token) (interface{}, error) {
	return []byte(key), nil
})

Более полный рабочий пример анализа токена JWT.

package main

import (
	"fmt"
	"log"
	"github.com/golang-jwt/jwt/v4"
)

const key = "my secure jwt key"

type UserClaim struct {
	jwt.RegisteredClaims
	ID    int 
	Email string
	Name  string
}

func main() {
	jwtToken := "a token generated from previous code"
	var userClaim UserClaim
	
    // 👇
	token, err := jwt.ParseWithClaims(jwtToken, &userClaim, func(token *jwt.Token) (interface{}, error) {
		return []byte(key), nil
	})
	if err != nil {
		log.Fatal(err)
	}

	// Checking token validity 
	if !token.Valid {
        log.Fatal("invalid token")
	}
    
    fmt.Printf("Parsed User Claim: %d %s %s\n", userClaim.ID, userClaim.Email, userClaim.Name)
}
main.go

Здесь следует обратить внимание на то, как jwt.ParseWithClaims принимает секретный ключ: в качестве третьего аргумента он принимает функцию, в которой вы должны вернуть ключ.

Вы можете проверить достоверность токена, используя Valid логическое значение.

Если токен действителен, UserClaim он будет заполнен информацией внутри токена.