Skip to content
This repository was archived by the owner on May 21, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ script:
- go test -v ./...

go:
- 1.3
- 1.4
- 1.5
- 1.6
- 1.7
- '1.10'
- 1.11
- tip
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,53 @@ This library uses descriptive error messages whenever possible. If you are not g
Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go).

The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in the documentation.

### Benchmarks

This section describe gains from migrating from `encoding/json` standard library to jsoniter (`github.com/json-iterator/go`).

TL;DR: pure JSON operations are about 30% faster, HS signing methods about 10% faster, whereas RSA signing methods remain
dominated by cryptographic operations, making the impact of JSON improvements negligible.

Note that the parsing benchmark uses a small token with only a few claims: for large complex tokens, actual benefit is larger.

`go version go1.12.6 linux/amd64, Intel Core [email protected] GHz (dual core)`

`$ go test -bench=. -benchmem -benchtime 10s`

##### Signing benchmarks

```
HS algorithms - Standard encoding/json

BenchmarkHS256Signing-4 10000000 2458 ns/op 1584 B/op 32 allocs/op
BenchmarkHS384Signing-4 5000000 2790 ns/op 1968 B/op 32 allocs/op
BenchmarkHS512Signing-4 5000000 2799 ns/op 2064 B/op 32 allocs/op

HS algorithms - jsoniter

BenchmarkHS256Signing-4 10000000 2036 ns/op 1308 B/op 26 allocs/op
BenchmarkHS384Signing-4 10000000 2359 ns/op 1693 B/op 26 allocs/op
BenchmarkHS512Signing-4 10000000 2412 ns/op 1789 B/op 26 allocs/op

RS algorithms - Standard encoding/json

BenchmarkRS256Signing-4 20000 926550 ns/op 49217 B/op 169 allocs/op
BenchmarkRS384Signing-4 20000 907624 ns/op 49329 B/op 169 allocs/op
BenchmarkRS512Signing-4 20000 941379 ns/op 49346 B/op 169 allocs/op

RS algorithms - jsoniter

BenchmarkRS256Signing-4 20000 920077 ns/op 48970 B/op 164 allocs/op
BenchmarkRS384Signing-4 20000 939164 ns/op 49082 B/op 164 allocs/op
BenchmarkRS512Signing-4 20000 913491 ns/op 49100 B/op 164 allocs/op
```

##### Claims parsing benchmark
`ParseUnverified` applied on small token with a few custom claims.

```
standard json lib BenchmarkParsing-4 2000000 6556 ns/op 4192 B/op 94 allocs/op
jsoniter BenchmarkParsing-4 3000000 4886 ns/op 3657 B/op 91 allocs/op
```

29 changes: 24 additions & 5 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package jwt_test

import (
"fmt"
"github.com/dgrijalva/jwt-go"
"time"

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

// Example (atypical) using the StandardClaims type by itself to parse a token.
Expand All @@ -22,8 +23,17 @@ func ExampleNewWithClaims_standardClaims() {

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err)
//Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.QsODzZu3lUZMVdhbO76u3Jv02iYCvEHcYVUI1kOWEU0 <nil>
if err != nil {
fmt.Printf("error: %v\n", err)
}

// signed string may differ depending on the order in which claims appear
var claimsAfterDecode jwt.StandardClaims
_, _ = jwt.ParseWithClaims(ss, &claimsAfterDecode, func(_ *jwt.Token) (interface{}, error) {
return mySigningKey, nil
})
fmt.Printf("%#v", claimsAfterDecode)
//Output: jwt.StandardClaims{Audience:"", ExpiresAt:15000, Id:"", IssuedAt:0, Issuer:"test", NotBefore:0, Subject:""}
}

// Example creating a token using a custom claims type. The StandardClaim is embedded
Expand All @@ -47,8 +57,17 @@ func ExampleNewWithClaims_customClaimsType() {

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err)
//Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c <nil>
if err != nil {
fmt.Printf("error: %v\n", err)
}

// signed string may differ depending on the order in which claims appear
var claimsAfterDecode MyCustomClaims
_, _ = jwt.ParseWithClaims(ss, &claimsAfterDecode, func(_ *jwt.Token) (interface{}, error) {
return mySigningKey, nil
})
fmt.Printf("%#v", claimsAfterDecode)
//Output: jwt_test.MyCustomClaims{Foo:"bar", StandardClaims:jwt.StandardClaims{Audience:"", ExpiresAt:15000, Id:"", IssuedAt:0, Issuer:"test", NotBefore:0, Subject:""}}
}

// Example creating a token using a custom claims type. The StandardClaim is embedded
Expand Down
19 changes: 15 additions & 4 deletions hmac_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package jwt_test

import (
"fmt"
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"time"

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

// For HMAC signing method, the key can be any []byte. It is recommended to generate
Expand Down Expand Up @@ -32,9 +33,19 @@ func ExampleNew_hmac() {

// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(hmacSampleSecret)
if err != nil {
fmt.Printf("error: %v\n", err)
}

fmt.Println(tokenString, err)
// Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU <nil>
// signed string may differ depending on the order in which claims appear
var claimsAfterDecode jwt.MapClaims
_, _ = jwt.ParseWithClaims(tokenString, &claimsAfterDecode, func(_ *jwt.Token) (interface{}, error) {
return hmacSampleSecret, nil
})

fmt.Printf("%v: %v ", "foo", claimsAfterDecode["foo"])
fmt.Printf("%v: %v", "nbf", claimsAfterDecode["nbf"])
// Output: foo: bar nbf: 1.4444784e+09
}

// Example parsing and validating a token using the HMAC signing method
Expand All @@ -51,7 +62,7 @@ func ExampleParse_hmac() {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}

// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
return hmacSampleSecret, nil
})
Expand Down
11 changes: 5 additions & 6 deletions map_claims.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package jwt

import (
"encoding/json"
stdjson "encoding/json"
"errors"
// "fmt"
)

// Claims type that uses the map[string]interface{} for JSON decoding
// MapClaims is a type of Claims that uses the map[string]interface{} for JSON decoding
// This is the default claims type if you don't supply one
type MapClaims map[string]interface{}

Expand All @@ -23,7 +22,7 @@ func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool {
switch exp := m["exp"].(type) {
case float64:
return verifyExp(int64(exp), cmp, req)
case json.Number:
case stdjson.Number:
v, _ := exp.Int64()
return verifyExp(v, cmp, req)
}
Expand All @@ -36,7 +35,7 @@ func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool {
switch iat := m["iat"].(type) {
case float64:
return verifyIat(int64(iat), cmp, req)
case json.Number:
case stdjson.Number:
v, _ := iat.Int64()
return verifyIat(v, cmp, req)
}
Expand All @@ -56,7 +55,7 @@ func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool {
switch nbf := m["nbf"].(type) {
case float64:
return verifyNbf(int64(nbf), cmp, req)
case json.Number:
case stdjson.Number:
v, _ := nbf.Int64()
return verifyNbf(v, cmp, req)
}
Expand Down
15 changes: 13 additions & 2 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,35 @@ package jwt

import (
"bytes"
"encoding/json"
"fmt"
"strings"

jsoniter "github.com/json-iterator/go"
)

var (
json jsoniter.API
)

func init() {
json = jsoniter.ConfigFastest
}

// Parser knows how to parse a JWT
type Parser struct {
ValidMethods []string // If populated, only these methods will be considered valid
UseJSONNumber bool // Use JSON Number format in JSON decoder
SkipClaimsValidation bool // Skip claims validation during token parsing
}

// Parse, validate, and return a token.
// Parse validates and returns a token.
// keyFunc will receive the parsed token and should return the key for validating.
// If everything is kosher, err will be nil
func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
}

// ParseWithClaims parses the token and hydrates Claims passed as parameter.
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
token, parts, err := p.ParseUnverified(tokenString, claims)
if err != nil {
Expand Down
42 changes: 42 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ var jwtTestData = []struct {
}

func TestParser_Parse(t *testing.T) {
testParse(t)
}

func testParse(t testing.TB) {
privateKey := test.LoadRSAPrivateKeyFromDisk("test/sample_key")

// Iterate over test data set and run tests
Expand Down Expand Up @@ -299,3 +303,41 @@ func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key interface{}) {
})

}

func BenchmarkParsing(b *testing.B) {
type MyCustomClaims struct {
Foo string `json:"foo"`
Bar uint32 `json:"bar"`
Float float64 `json:"float"`
Groups []string `json:"groups"`
Roles map[string][]string `json:"roles"`
jwt.StandardClaims
}

//keyfunc := defaultKeyFunc
claims := &MyCustomClaims{
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Second * 10).Unix(),
},
Foo: "foo",
Bar: 123,
Float: 1.5,
Groups: []string{"A", "B", "C"},
Roles: map[string][]string{
"app": {"x", "y", "z"},
"master": {"X", "Y", "Z"},
},
}
privateKey := test.LoadRSAPrivateKeyFromDisk("test/sample_key")
tokenString := test.MakeSampleToken(claims, privateKey)
parser := &jwt.Parser{UseJSONNumber: true}

b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
token, _, err := parser.ParseUnverified(tokenString, jwt.MapClaims{})
if token == nil || err != nil {
b.FailNow()
}
}
})
}
1 change: 0 additions & 1 deletion token.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package jwt

import (
"encoding/base64"
"encoding/json"
"strings"
"time"
)
Expand Down