Documentation ¶
Overview ¶
Package jwt предоставляет возможности для удобного создания и проверки токенов в формате JWT.
Поддерживаются алгоритмы HS256, RS256 и ES256.
Делалось исключительно для себя и подход принципиально отличается от большинства существующих библиотек для работы с JWT: в первую очередь я пытался облегчить работу с токенами по типичному сценарию. Специально для генерации большого количества однотипных токенов сделан класс jwt.Config, который позволяет описать основные поля и временные метки токена, а потом быстро создавать токены, передавая только дополнительные данные.
Кроме того, мне очень не понравился подход в других библиотеках по созданию разных объектов для подписи разными алгоритмами, а особенно их "многословность". Здесь все просто: ключ абстрактного формата, а при генерации подписи автоматически используется подходящий алгоритм. Благодаря этому подходу все вспомогательные классы просто скрыты и не пугают выбором и настройкой.
Так же в библиотеку добавлены некоторые вспомогательные функции, которые могут пригодиться при работе с токенами:
- Time для разбора и представления времени в виде числа, а не строки;
- JSON для быстрого описания полей токена, когда не хочется создавать специально для этого структуру с описанием полей;
- NewRS256Key() и NewES256Key() для быстрой генерации ключей в формате RSA и ECDSA;
- Nonce() для генерации случайных строковых последовательностей заданной длины;
- Keys для работы со списками публичных ключей в формате JWKS.
Index ¶
- Variables
- func Decode(token string, claimset interface{}) error
- func Encode(claimset, key interface{}) (string, error)
- func NewES256Key() *ecdsa.PrivateKey
- func NewHS256Key(length int) []byte
- func NewRS256Key() *rsa.PrivateKey
- func Nonce(length uint8) func() string
- func Verify(token string, key interface{}) (claim []byte, err error)
- type Config
- type JSON
- type JWK
- type Time
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrEmptySignKey = errors.New("empty token sign key") ErrInvalid = errors.New("invalid token") ErrBadType = errors.New("bad token type") ErrNotSigned = errors.New("token not signed") ErrCreatedAfterNow = errors.New("token created after now") ErrNotBeforeNow = errors.New("token not before now") ErrExpired = errors.New("token expired") ErrBadHashFunc = errors.New("hash function for key is not available") )
Ошибки создания и верификации токенов.
var RSAKeyBits = 2048
RSAKeyBits содержит длину ключа для генерации RSA. Используется в функции NewRS256Key.
Functions ¶
func Decode ¶
Decode декодирует содержимое токена в claimset. По ходу распаковки проверяются основные временные поля токена, но не подпись токена.
Example ¶
package main import ( "fmt" "log" "time" "github.com/mdigger/jwt" ) var secret = "my secret sign key" func GenerateTestToken() string { claimset := jwt.JSON{ "iss": "http://service.example.com/", "sub": "2934852845", "iat": jwt.Time{Time: time.Now()}, "exp": jwt.Time{Time: time.Now().Add(time.Hour)}, "name": "Dmitry Sedykh", "email": "dmitrys@example.com", "birthday": jwt.Time{Time: time.Date(1971, time.December, 24, 8, 43, 0, 0, time.UTC)}, "nonce": jwt.Nonce(8)(), } var err error token, err := jwt.Encode(claimset, secret) if err != nil { panic(err) } return token } func main() { token := GenerateTestToken() // описываем структуру с данными, которые хотим распаковать из токена var claimset struct { Issuer string `json:"iss"` Subject string `json:"sub"` Created jwt.Time `json:"iat"` Expired jwt.Time `json:"exp"` Name string Email string Birthday jwt.Time // время представлено числовом виде Nonce string } // извлекаем данные из токена if err := jwt.Decode(token, &claimset); err != nil { log.Fatal(err) } fmt.Printf("%+v\n", claimset) }
Output:
func Encode ¶
Encode возвращает подписанный с помощью ключа key токен из claimset. Если ключ не указан, то токен не будет подписан. Key может быть представлен в виде строки или ключа для RSA или ECDSA. А может быть представлен в виде функции, которая возвращает нужный ключ. Поддерживаются следующие форматы ключа:
*rsa.PrivateKey *ecdsa.PrivateKey string []byte fmt.Stringer
Так же поддерживаются следующие форматы функции для передачи ключа:
func() interface{} func() string, interface{}
В последних случаях, кроме ключа, так же возвращается его идентификатор.
Example ¶
package main import ( "fmt" "log" "time" "github.com/mdigger/jwt" ) var secret = "my secret sign key" func main() { // описываем данные токена (не обязательно в JSON) claimset := jwt.JSON{ "iss": "http://service.example.com/", "sub": "2934852845", "iat": jwt.Time{Time: time.Now()}, "exp": jwt.Time{Time: time.Now().Add(time.Hour)}, "name": "Dmitry Sedykh", "email": "dmitrys@example.com", "birthday": jwt.Time{Time: time.Date(1971, time.December, 24, 0, 0, 0, 0, time.Local)}, "nonce": jwt.Nonce(8)(), } // создаем токен с подписью HS256 с секретным ключом token, err := jwt.Encode(claimset, secret) if err != nil { log.Fatal(err) } fmt.Println("token:", token) }
Output:
func NewES256Key ¶
func NewES256Key() *ecdsa.PrivateKey
NewES256Key возвращает новый ключ для подписи в формате ESxxx. По умолчанию возвращается ES256 ключ, но это можно изменить через переменную ECDSACurve.
Вызывает panic в случае ошибки создания.
func NewHS256Key ¶
NewHS256Key возвращает новый ключ для подписи в формате HS256 указанной длины.
Вызывает panic в случае ошибки создания.
func NewRS256Key ¶
func NewRS256Key() *rsa.PrivateKey
NewRS256Key возвращает новый ключ для подписи в формате RS256. Длина ключа задается переменной RSAKeyBits.
Вызывает panic в случае ошибки создания.
func Nonce ¶
Nonce возвращает функцию, которая генерирует случайные псевдо-уникальные строки заданного размера. Можно использовать ее для создания уникальных значений поля nonce токенов.
Example ¶
package main import ( "fmt" "log" "github.com/mdigger/jwt" ) func main() { conf := jwt.Config{ Issuer: "http://service.example.com/", UniqueID: jwt.Nonce(8), // задаем функцию генерации случайного nonce } token, err := conf.Token(jwt.JSON{"sub": "9394203942934"}) if err != nil { log.Fatal(err) } fmt.Println(token) }
Output:
func Verify ¶
Verify проверяет подпись токена. В качестве параметра передается ключ для проверки подписи или функция, принимающая одно или два строковых значения (алгоритм и идентификатор ключа) и возвращающая для них ключ.
Сам ключ может быть в указан в следующих форматах:
*rsa.PrivateKey *rsa.PublicKey *ecdsa.PrivateKey *ecdsa.PublicKey string []byte fmt.Stringer
Так же поддерживаются следующие форматы функции для передачи ключа:
func(keyID string, alg string) interface{} func(keyID string) interface{}
Кроме проверки подписи, проверяются основные даты токена, что он актуален на данный момент.
Возвращается неразобранное содержимое токена.
Example ¶
package main import ( "log" "time" "github.com/mdigger/jwt" ) var secret = "my secret sign key" func GenerateTestToken() string { claimset := jwt.JSON{ "iss": "http://service.example.com/", "sub": "2934852845", "iat": jwt.Time{Time: time.Now()}, "exp": jwt.Time{Time: time.Now().Add(time.Hour)}, "name": "Dmitry Sedykh", "email": "dmitrys@example.com", "birthday": jwt.Time{Time: time.Date(1971, time.December, 24, 8, 43, 0, 0, time.UTC)}, "nonce": jwt.Nonce(8)(), } var err error token, err := jwt.Encode(claimset, secret) if err != nil { panic(err) } return token } func main() { token := GenerateTestToken() // проверка подписи токена с простым ключом if _, err := jwt.Verify(token, secret); err != nil { log.Fatal(err) } // описываем функцию, которая будет возвращать наш ключ в зависимости от // алгоритма и идентификатора ключа getMyKey := func(keyID, alg string) []byte { if alg != "HS256" || keyID != "" { return nil } return []byte(secret) } // вызываем проверку подписи с вызовом функции получения ключа if _, err := jwt.Verify(token, getMyKey); err != nil { log.Fatal(err) } }
Output:
Types ¶
type Config ¶
type Config struct { Issuer string // iss - идентификатор выпускающего Created bool // iat - добавлять время создания Expires time.Duration // exp - добавлять время жизни NotBefore time.Duration // nbf - добавлять время начала действия Type string // typ - тип токена UniqueID func() string // nonce - генератор случайной строки Private JSON // дополнительные именованные поля Key interface{} // ключ для подписи токена или функция его возвращающая }
Config описывает шаблон для генерации токена. Поля, указанные в нем, будут автоматически добавляться при генерации токена. Любые общие дополнительные поля для токена можно указать в Private. Если задана функция Nonce, то в каждый токен будет добавляться уникальная строка, возвращаемая этой функцией.
Для автоматической подписи токена необходимо указать ключ. В зависимости от его типа будут использоваться разные алгоритмы для генерации подписи. Для *rsa.PrivateKey будет создаваться токен с алгоритмом подписи RS256. Для *ecdsa.PrivateKey - ES256, ES384 или ES512, в зависимости от параметров созданного ключа. Для string, []byte или любого другого объекта, который поддерживает строковое представление (fmt.Stringer) - HS256.
Чтобы указать в заголовке токена идентификатор ключа, используемого для подписи, нужно чтобы функция ключа возвращала два значения: первым будет KeyID, а вторым - сам ключ для подписи.
Example ¶
package main import ( "fmt" "log" "time" "github.com/mdigger/jwt" ) func main() { // инициализируем шаблон с описанием основных полей токена conf := jwt.Config{ Issuer: "http://test.example.com/auth", Created: true, // добавлять дату создания Expires: time.Hour, // время жизни UniqueID: jwt.Nonce(8), // добавлять nonce Private: jwt.JSON{"temp": true}, // дополнительные поля Key: jwt.NewRS256Key(), // ключ для подписи } // создаем токен с указанными полями token, err := conf.Token(jwt.JSON{ "sub": "938102384109384", "email": "user@example.com", "name": "Test User", "birthday": time.Date(1971, time.December, 24, 0, 0, 0, 0, time.Local), "temp": false, // переопределяем поля в шаблоне }) if err != nil { log.Fatal(err) } fmt.Println(token) }
Output:
func (Config) Token ¶
Token возвращает сгенерированный токен на основании шаблона и предоставленных данных. В качестве payload можно указать map[string]interface{} или собственный объект. Так же принимается строка: в этом случае считается, что она задает единственное значение "sub".
Есть несколько отличий от стандартной сериализации в формат JSON, которые заложены в этой функции. Во-первых, все поля, представленные в виде time.Time кодируются в числовом виде, а не строками, как это стандартно происходит в Go. А не заданные значение time.Time автоматически игнорируются. Во-вторых, в структурах для именования полей могут использоваться теги "json" и "jwt". Последний имеет чуть больший приоритет, поэтому вы можете специально для токенов указывать другие имена. Если имя поля не определено в теге, то используется само имя поля, но первая его буква при этом становится строчной, что больше соответствует формату JSON токена.
type JSON ¶
type JSON = map[string]interface{}
JSON является синонимом для быстрого создания map в формате JSON.
Example ¶
package main import ( "fmt" "log" "time" "github.com/mdigger/jwt" ) func main() { // определяем содержимое токена claimset := jwt.JSON{ "iss": "http://service.example.com/", "sub": "2934852845", "iat": jwt.Time{Time: time.Now()}, "exp": jwt.Time{Time: time.Now().Add(time.Hour)}, "name": "Dmitry Sedykh", "email": "dmitrys@example.com", "birthday": jwt.Time{Time: time.Date(1971, time.December, 24, 0, 0, 0, 0, time.Local)}, "nonce": jwt.Nonce(8)(), } // создаем токен без подписи token, err := jwt.Encode(claimset, nil) if err != nil { log.Fatal(err) } fmt.Println("token:", token) }
Output:
type JWK ¶
type JWK struct { // The "kty" (key type) parameter identifies the cryptographic algorithm // family used with the key, such as "RSA" or "EC". Type string `json:"kty"` // The "use" (public key use) parameter identifies the intended use of // the public key. The "use" parameter is employed to indicate whether // a public key is used for encrypting data or verifying the signature // on data. // // Values defined by this specification are: // - "sig" (signature) // - "enc" (encryption) Usage string `json:"use,omitempty"` // The "key_ops" (key operations) parameter identifies the operation(s) // for which the key is intended to be used. The "key_ops" parameter is // intended for use cases in which public, private, or symmetric keys // may be present. // // Its value is an array of key operation values. Values defined by // this specification are: // - "sign" (compute digital signature or MAC) // - "verify" (verify digital signature or MAC) // - "encrypt" (encrypt content) // - "decrypt" (decrypt content and validate decryption, if applicable) // - "wrapKey" (encrypt key) // - "unwrapKey" (decrypt key and validate decryption, if applicable) // - "deriveKey" (derive key) // - "deriveBits" (derive bits not to be used as a key) KeyOps []string `json:"key_ops,omitempty"` // The "alg" (algorithm) parameter identifies the algorithm intended for // use with the key. Algorithm string `json:"alg"` // The "kid" (key ID) parameter is used to match a specific key. This // is used, for instance, to choose among a set of keys within a JWK Set // during key rollover. ID string `json:"kid,omitempty"` // ECDSA Public Curve string `json:"crv,omitempty"` // Curve X string `json:"x,omitempty"` // X Coordinate Y string `json:"y,omitempty"` // Y Coordinate // RSA Public N string `json:"n,omitempty"` // Modulus E string `json:"e,omitempty"` // Exponent // Private RSA & ECDSA D string `json:"d,omitempty"` // ECC Private Key or RSA Private Exponent // RSA Private P string `json:"p,omitempty"` // First Prime Factor Q string `json:"q,omitempty"` // Second Prime Factor DP string `json:"dp,omitempty"` // First Factor CRT Exponent DQ string `json:"dq,omitempty"` // Second Factor CRT Exponent QI string `json:"qi,omitempty"` // First CRT Coefficient OTH []rsaCtr `json:"oth,omitempty"` // Other Primes Info // HS K string `json:"k,omitempty"` }
JWK описывает формат данных ключа.
The "use" and "key_ops" JWK members SHOULD NOT be used together. Mimetype: application/jwk+json (application/jwk-set+json)
type Time ¶
Time подменяет собой стандартное time.Time, но переопределяет для него формат представления и распаковки из JSON в виде числа. Во всем остальном ведет себя как стандартный time.Time.
Example ¶
package main import ( "fmt" "log" "time" "github.com/mdigger/jwt" ) var secret = "my secret sign key" func GenerateTestToken() string { claimset := jwt.JSON{ "iss": "http://service.example.com/", "sub": "2934852845", "iat": jwt.Time{Time: time.Now()}, "exp": jwt.Time{Time: time.Now().Add(time.Hour)}, "name": "Dmitry Sedykh", "email": "dmitrys@example.com", "birthday": jwt.Time{Time: time.Date(1971, time.December, 24, 8, 43, 0, 0, time.UTC)}, "nonce": jwt.Nonce(8)(), } var err error token, err := jwt.Encode(claimset, secret) if err != nil { panic(err) } return token } func main() { token := GenerateTestToken() // описываем структуру с данными, которые хотим распаковать из токена var claimset struct { Issuer string `json:"iss"` Subject string `json:"sub"` Created jwt.Time `json:"iat"` Expired jwt.Time `json:"exp"` Name string Email string Birthday jwt.Time // время представлено числовом виде Nonce string } // извлекаем данные из токена if err := jwt.Decode(token, &claimset); err != nil { log.Fatal(err) } fmt.Println(claimset.Birthday.UTC()) }
Output: 1971-12-24 08:43:00 +0000 UTC
func (Time) MarshalJSON ¶
MarshalJSON представляет время в формате JSON в виде числа.
func (*Time) UnmarshalJSON ¶
UnmarshalJSON восстанавливает время, представленное в формате JSON в виде числа.