Продвинутые примеры
Асимметричная подпись RSA
Использование RSA приватного ключа для подписи и публичного для проверки. Подходит для микросервисной архитектуры, где проверяющей стороне не нужен приватный ключ.
go
package main
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"log"
"github.com/cybergodev/jwt"
)
func main() {
// Генерация пары RSA-ключей (в реальном использовании загружайте из файла)
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatal(err)
}
cfg := jwt.DefaultConfig()
cfg.SigningMethod = jwt.SigningMethodRS256
cfg.SigningKey = privateKey
cfg.VerificationKey = &privateKey.PublicKey // Необязательно, если не указано, используется SigningKey
processor, err := jwt.New(cfg)
if err != nil {
panic(err)
}
defer processor.Close()
claims := &jwt.Claims{UserID: "user456", Username: "bob"}
token, err := processor.Create(claims)
if err != nil {
panic(err)
}
fmt.Println("RSA Token:", token)
parsed, valid, err := processor.Validate(token)
if err != nil {
panic(err)
}
fmt.Println("Valid:", valid)
fmt.Println("UserID:", parsed.UserID)
}Асимметричная подпись ECDSA
Использование ECDSA на эллиптических кривых — более короткие ключи и лучшая производительность.
go
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"fmt"
"log"
"github.com/cybergodev/jwt"
)
func main() {
// Генерация пары ECDSA-ключей
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
cfg := jwt.DefaultConfig()
cfg.SigningMethod = jwt.SigningMethodES256
cfg.SigningKey = privateKey
processor, err := jwt.New(cfg)
if err != nil {
panic(err)
}
defer processor.Close()
claims := &jwt.Claims{UserID: "user789", Username: "charlie"}
token, err := processor.Create(claims)
if err != nil {
panic(err)
}
fmt.Println("ECDSA Token:", token)
}Пользовательские Claims
Определение собственной структуры Claims с бизнес-полями.
go
package main
import (
"errors"
"fmt"
"github.com/cybergodev/jwt"
)
// Пользовательская структура Claims
type MyClaims struct {
UserID string `json:"user_id"`
Email string `json:"email"`
Role string `json:"role"`
jwt.RegisteredClaims
}
func (c *MyClaims) GetRegisteredClaims() *jwt.RegisteredClaims {
return &c.RegisteredClaims
}
func (c *MyClaims) Validate() error {
if c.UserID == "" {
return errors.New("user_id is required")
}
if c.Email == "" {
return errors.New("email is required")
}
return nil
}
func main() {
cfg := jwt.DefaultConfig()
cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
processor, err := jwt.New(cfg)
if err != nil {
panic(err)
}
defer processor.Close()
claims := &MyClaims{
UserID: "user123",
Email: "[email protected]",
Role: "admin",
}
// Создание токена
token, err := processor.Create(claims)
if err != nil {
panic(err)
}
fmt.Println("Token:", token)
// Проверка в пользовательские Claims
myClaims := &MyClaims{}
result, valid, err := processor.ValidateInto(token, myClaims)
if err != nil {
panic(err)
}
if valid {
parsed := result.(*MyClaims)
fmt.Println("UserID:", parsed.UserID) // Вывод: user123
fmt.Println("Email:", parsed.Email) // Вывод: [email protected]
}
// Обновление в пользовательские Claims
refreshToken, err := processor.CreateRefresh(claims)
if err != nil {
panic(err)
}
newToken, err := processor.RefreshInto(refreshToken, &MyClaims{})
if err != nil {
panic(err)
}
fmt.Println("New Token:", newToken)
}Пользовательский бэкенд чёрного списка (Redis)
go
package main
import (
"context"
"fmt"
"time"
"github.com/cybergodev/jwt"
)
// RedisBlacklistStore реализует интерфейс BlacklistStore
// Примечание: при реальном использовании необходим Redis-клиент (например, github.com/redis/go-redis)
type RedisBlacklistStore struct {
// client *redis.Client
}
func (s *RedisBlacklistStore) Add(tokenID string, expiresAt time.Time) error {
ttl := time.Until(expiresAt)
if ttl <= 0 {
return nil
}
// return s.client.Set(context.Background(), "blacklist:"+tokenID, "1", ttl).Err()
fmt.Printf("Redis ADD: %s, TTL: %v\n", tokenID, ttl)
return nil
}
func (s *RedisBlacklistStore) Contains(tokenID string) (bool, error) {
// return s.client.Exists(context.Background(), "blacklist:"+tokenID).Result()
return false, nil
}
func (s *RedisBlacklistStore) Close() error {
// return s.client.Close()
return nil
}
func main() {
_ = context.Background() // Сохраняем импорт context доступным (раскомментируйте Redis-вызовы при реальном использовании)
cfg := jwt.DefaultConfig()
cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
cfg.Blacklist.Store = &RedisBlacklistStore{}
processor, err := jwt.New(cfg)
if err != nil {
panic(err)
}
defer processor.Close()
claims := &jwt.Claims{UserID: "user123", Username: "alice"}
token, err := processor.Create(claims)
if err != nil {
panic(err)
}
err = processor.Revoke(token)
if err != nil {
panic(err)
}
fmt.Println("Token revoked via Redis backend")
}Внедрение часов (сценарий тестирования)
Использование FixedClock для контроля времени в тестах.
go
package main
import (
"fmt"
"time"
"github.com/cybergodev/jwt"
)
func main() {
fixedTime := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
cfg := jwt.DefaultConfig()
cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
cfg.Clock = jwt.FixedClock{T: fixedTime}
processor, err := jwt.New(cfg)
if err != nil {
panic(err)
}
defer processor.Close()
claims := &jwt.Claims{UserID: "user123", Username: "alice"}
token, err := processor.Create(claims)
if err != nil {
panic(err)
}
parsed, _, err := processor.Validate(token)
if err != nil {
panic(err)
}
fmt.Println("IssuedAt:", parsed.IssuedAt.Time) // Вывод: 2026-01-01 00:00:00 +0000 UTC
fmt.Println("ExpiresAt:", parsed.ExpiresAt.Time) // Вывод: 2026-01-01 00:15:00 +0000 UTC
}Разбор непроверенного токена
Извлечение информации из Claims без проверки подписи для отладки или логирования.
go
package main
import (
"fmt"
"github.com/cybergodev/jwt"
)
func main() {
cfg := jwt.DefaultConfig()
cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
processor, err := jwt.New(cfg)
if err != nil {
panic(err)
}
defer processor.Close()
claims := &jwt.Claims{UserID: "user123", Username: "alice"}
token, err := processor.Create(claims)
if err != nil {
panic(err)
}
// Разбор без проверки подписи
parsed := &jwt.Claims{}
err = processor.ParseUnverified(token, parsed)
if err != nil {
panic(err)
}
fmt.Println("UserID (unverified):", parsed.UserID)
}Полный пример веб-сервиса
go
package main
import (
"fmt"
"log"
"net/http"
"strings"
"github.com/cybergodev/jwt"
)
var processor *jwt.Processor
func main() {
cfg := jwt.DefaultConfig()
cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
cfg.Issuer = "my-web-service"
cfg.ExpectedAudience = "my-app"
var err error
processor, err = jwt.New(cfg)
if err != nil {
panic(err)
}
defer processor.Close()
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/protected", handleProtected)
fmt.Println("Server running on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
// В реальном сценарии проверка имени пользователя и пароля
claims := &jwt.Claims{
UserID: "user123",
Username: "alice",
Role: "admin",
}
accessToken, err := processor.Create(claims)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
refreshToken, err := processor.CreateRefresh(claims)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
fmt.Fprintf(w, `{"access_token":"%s","refresh_token":"%s"}`, accessToken, refreshToken)
}
func handleProtected(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth == "" || !strings.HasPrefix(auth, "Bearer ") {
http.Error(w, "missing token", http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(auth, "Bearer ")
claims, valid, err := processor.Validate(tokenString)
if err != nil || !valid {
http.Error(w, "invalid token", http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "Hello, %s (ID: %s, Role: %s)",
claims.Username, claims.UserID, claims.Role)
}