JWT в Fiber
Промежуточное ПО JWT возвращает промежуточное программное обеспечение аутентификации JSON Web Token (JWT). При наличии действительного токена оно устанавливает пользователя в Ctx.Locals и вызывает следующий обработчик. При недействительном токене возвращается ошибка "401 - Неавторизован". При отсутствии токена - ошибка "400 - Неверный запрос".
Примечание: Требуется Go версии 1.19 или выше
Установка
Это промежуточное ПО поддерживает Fiber v1 и v2, установите соответственно.
go get -u github.com/gofiber/fiber/v2
go get -u github.com/gofiber/contrib/jwt
go get -u github.com/golang-jwt/jwt/v5
Подпись
jwtware.New(config ...jwtware.Config) func(*fiber.Ctx) error
Конфигурация
Свойство | Тип | Описание | Значение по умолчанию |
---|---|---|---|
Filter | func(*fiber.Ctx) bool |
Определяет функцию для пропуска промежуточного ПО | nil |
SuccessHandler | func(*fiber.Ctx) error |
Определяет функцию для выполнения при действительном токене | nil |
ErrorHandler | func(*fiber.Ctx, error) error |
Определяет функцию для выполнения при недействительном токене | 401 Недействительный или истекший JWT |
SigningKey | interface{} |
Ключ подписи, используемый для проверки токена. Если длина SigningKeys равна 0, он используется в качестве запасного варианта | nil |
SigningKeys | map[string]interface{} |
Соответствие используемых ключей подписи для проверки токенов с полем kid |
nil |
ContextKey | string |
Ключ контекста, используемый для хранения информации о пользователе из токена в контексте | "user" |
Claims | jwt.Claim |
Расширенные данные утверждений, определяющие содержимое токена | jwt.MapClaims{} |
TokenLookup | string |
Строка в формате : , используемая для разбора токена |
"header:Authorization" |
AuthScheme | string |
Используемая AuthScheme в заголовке Authorization. Значение по умолчанию ("Bearer") используется только с значением TokenLookup по умолчанию |
"Bearer" |
KeyFunc | func() jwt.Keyfunc |
Пользовательская функция предоставления открытого ключа для проверки токена | jwtKeyFunc |
JWKSetURLs | []string |
Массив уникальных URL-адресов JSON Web Key (JWK) Set, используемых для разбора JWT | nil |
Пример использования HS256
package main
import (
"time"
"github.com/gofiber/fiber/v2"
jwtware "github.com/gofiber/contrib/jwt"
"github.com/golang-jwt/jwt/v5"
)
func main() {
app := fiber.New()
// Маршрут входа
app.Post("/login", login)
// Доступный маршрут без аутентификации
app.Get("/", accessible)
// Промежуточное ПО JWT
app.Use(jwtware.New(jwtware.Config{
SigningKey: jwtware.SigningKey{Key: []byte("секрет")},
}))
// Ограниченный маршрут
app.Get("/restricted", restricted)
app.Listen(":3000")
}
func login(c *fiber.Ctx) error {
user := c.FormValue("user")
pass := c.FormValue("pass")
// Генерация ошибки неавторизованного доступа
if user != "john" || pass != "doe" {
return c.SendStatus(fiber.StatusUnauthorized)
}
// Создание утверждений
claims := jwt.MapClaims{
"name": "John Doe",
"admin": true,
"exp": time.Now().Add(time.Hour * 72).Unix(),
}
// Создание токена
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Генерирование закодированного токена и отправка в качестве ответа
t, err := token.SignedString([]byte("секрет"))
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(fiber.Map{"token": t})
}
func accessible(c *fiber.Ctx) error {
return c.SendString("Доступно")
}
func restricted(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
name := claims["name"].(string)
return c.SendString("Добро пожаловать, " + name)
}
Тест HS256
Войдите с использованием имени пользователя и пароля, чтобы получить токен.
curl --data "user=john&pass=doe" http://localhost:3000/login
Ответ
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"
}
Запрос ограниченного ресурса с использованием токена в заголовке авторизации.
curl localhost:3000/restricted -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"
Ответ
Добро пожаловать John Doe
package main
import (
"crypto/rand"
"crypto/rsa"
"log"
"time"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
jwtware "github.com/gofiber/contrib/jwt"
)
var (
// Очевидно, что это всего лишь тестовый пример. Не используйте это в продакшене.
// В продакшене вы должны заранее создавать пару ключей.
// Никогда не добавляйте приватный ключ в репозиторий GitHub.
privateKey *rsa.PrivateKey
)
func main() {
app := fiber.New()
// Для демонстрации создается новая пара закрытого/открытого ключа при каждом запуске программы. См. примечание выше.
rng := rand.Reader
var err error
privateKey, err = rsa.GenerateKey(rng, 2048)
if err != nil {
log.Fatalf("rsa.GenerateKey:%v", err)
}
// Маршрут для входа
app.Post("/login", login)
// Маршрут для незарегистрированных пользователей
app.Get("/", accessible)
// Промежуточное ПО JWT
app.Use(jwtware.New(jwtware.Config{
SigningKey: jwtware.SigningKey{
JWTAlg: jwtware.RS256,
Key: privateKey.Public(),
},
}))
// Ограниченный маршрут
app.Get("/restricted", restricted)
app.Listen(":3000")
}
func login(c *fiber.Ctx) error {
user := c.FormValue("user")
pass := c.FormValue("pass")
// Прекратить работу из-за отказа в авторизации
if user != "john" || pass != "doe" {
return c.SendStatus(fiber.StatusUnauthorized)
}
// Создать утверждения
claims := jwt.MapClaims{
"name": "John Doe",
"admin": true,
"exp": time.Now().Add(time.Hour * 72).Unix(),
}
// Создать токен
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
// Создать закодированный токен и отправить его в качестве ответа
t, err := token.SignedString(privateKey)
if err != nil {
log.Printf("token.SignedString:%v", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(fiber.Map{"token": t})
}
func accessible(c *fiber.Ctx) error {
return c.SendString("Доступно")
}
func restricted(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
name := claims["name"].(string)
return c.SendString("Добро пожаловать " + name)
}
Тест RS256
RS256 по сути то же самое, что и тест HS256 выше.
Тест JWK Set
Эти тесты такие же, как и основные тесты JWT выше, но они требуют действительный набор открытых ключей в формате JWK Set по адресам JWKSetURLs.
Пример пользовательской функции KeyFunc
KeyFunc определяет пользовательскую функцию, используемую для предоставления открытых ключей для проверки токена. Эта функция отвечает за проверку алгоритма подписи и выбор правильного ключа. Если токен был выдан внешней стороной, пользовательская функция KeyFunc может быть полезной.
При предоставлении пользовательской функции KeyFunc параметры SigningKey, SigningKeys и SigningMethod будут проигнорированы. Это один из трех вариантов предоставления ключей для проверки токена. Приоритетный порядок предоставления: пользовательская функция KeyFunc, SigningKeys и SigningKey. Если ни параметры SigningKeys, ни SigningKey не были предоставлены, эта функция должна быть обязательно предоставлена. По умолчанию проверка алгоритма подписи и выбор соответствующего ключа выполняется внутренней реализацией.
package main
import (
"fmt"
"github.com/gofiber/fiber/v2"
jwtware "github.com/gofiber/contrib/jwt"
"github.com/golang-jwt/jwt/v5"
)
func main() {
app := fiber.New()
app.Use(jwtware.New(jwtware.Config{
KeyFunc: customKeyFunc(),
}))
app.Get("/ok", func(c *fiber.Ctx) error {
return c.SendString("OK")
})
}
func customKeyFunc() jwt.Keyfunc {
return func(t *jwt.Token) (interface{}, error) {
// Проверяем, что метод подписи верный
if t.Method.Alg() != jwtware.HS256 {
return nil, fmt.Errorf("Неожиданный метод подписи JWT=%v", t.Header["alg"])
}
// TODO Реализовать загрузку пользовательского ключа подписи, например, загрузка из базы данных
signingKey := "секрет"
return []byte(signingKey), nil
}
}