forked from mirror/jwt
Compare commits
13 Commits
validation
...
main
Author | SHA1 | Date |
---|---|---|
re | 29918af7f7 | |
Alexander Yastrebov | 9358574a7a | |
Christian Banse | 2f0984a28b | |
Christian Banse | 2101c1f4bc | |
Krouton | 35053d4e20 | |
Jacob Kopczynski | 0c4e387985 | |
Christian Banse | bfea432b1a | |
Michael Fridman | d81acbf7f3 | |
Hugo | fdaf0eb0e0 | |
KroKite | f2878bb94b | |
George Kechagias | 9294af54b5 | |
Qian Qiao | 2da0bf7566 | |
Christian Banse | 8fb42696ff |
|
@ -25,7 +25,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go: [1.16, 1.17, 1.18]
|
||||
go: [1.17, 1.18, 1.19]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
@ -33,6 +33,8 @@ jobs:
|
|||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "${{ matrix.go }}"
|
||||
check-latest: true
|
||||
cache: true
|
||||
- name: Check Go code formatting
|
||||
run: |
|
||||
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
|
||||
|
@ -42,6 +44,7 @@ jobs:
|
|||
fi
|
||||
- name: Build
|
||||
run: |
|
||||
go install github.com/mfridman/tparse@latest
|
||||
go vet ./...
|
||||
go test -v ./...
|
||||
go test -v -race -count=1 -json -coverpkg=$(go list ./...) ./... | tparse -follow -notests
|
||||
go build ./...
|
||||
|
|
|
@ -2,18 +2,18 @@
|
|||
|
||||
Starting from [v4.0.0](https://github.com/golang-jwt/jwt/releases/tag/v4.0.0), the import path will be:
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
|
||||
The `/v4` version will be backwards compatible with existing `v3.x.y` tags in this repo, as well as
|
||||
`github.com/dgrijalva/jwt-go`. For most users this should be a drop-in replacement, if you're having
|
||||
troubles migrating, please open an issue.
|
||||
|
||||
You can replace all occurrences of `github.com/dgrijalva/jwt-go` or `github.com/golang-jwt/jwt` with `github.com/golang-jwt/jwt/v4`, either manually or by using tools such as `sed` or `gofmt`.
|
||||
You can replace all occurrences of `github.com/dgrijalva/jwt-go` or `github.com/golang-jwt/jwt` with `git.internal/re/jwt/v4`, either manually or by using tools such as `sed` or `gofmt`.
|
||||
|
||||
And then you'd typically run:
|
||||
|
||||
```
|
||||
go get github.com/golang-jwt/jwt/v4
|
||||
go get git.internal/re/jwt/v4
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
|
|
26
README.md
26
README.md
|
@ -1,7 +1,7 @@
|
|||
# jwt-go
|
||||
|
||||
[![build](https://github.com/golang-jwt/jwt/actions/workflows/build.yml/badge.svg)](https://github.com/golang-jwt/jwt/actions/workflows/build.yml)
|
||||
[![Go Reference](https://pkg.go.dev/badge/github.com/golang-jwt/jwt/v4.svg)](https://pkg.go.dev/github.com/golang-jwt/jwt/v4)
|
||||
[![Go Reference](https://pkg.go.dev/badge/git.internal/re/jwt/v4.svg)](https://pkg.go.dev/git.internal/re/jwt/v4)
|
||||
|
||||
A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](https://datatracker.ietf.org/doc/html/rfc7519).
|
||||
|
||||
|
@ -41,22 +41,22 @@ This library supports the parsing and verification as well as the generation and
|
|||
1. To install the jwt package, you first need to have [Go](https://go.dev/doc/install) installed, then you can use the command below to add `jwt-go` as a dependency in your Go program.
|
||||
|
||||
```sh
|
||||
go get -u github.com/golang-jwt/jwt/v4
|
||||
go get -u git.internal/re/jwt/v4
|
||||
```
|
||||
|
||||
2. Import it in your code:
|
||||
|
||||
```go
|
||||
import "github.com/golang-jwt/jwt/v4"
|
||||
import "git.internal/re/jwt/v4"
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt/v4) for examples of usage:
|
||||
See [the project documentation](https://pkg.go.dev/git.internal/re/jwt/v4) for examples of usage:
|
||||
|
||||
* [Simple example of parsing and validating a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-Parse-Hmac)
|
||||
* [Simple example of building and signing a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-New-Hmac)
|
||||
* [Directory of Examples](https://pkg.go.dev/github.com/golang-jwt/jwt#pkg-examples)
|
||||
* [Simple example of parsing and validating a token](https://pkg.go.dev/git.internal/re/jwt/v4#example-Parse-Hmac)
|
||||
* [Simple example of building and signing a token](https://pkg.go.dev/git.internal/re/jwt/v4#example-New-Hmac)
|
||||
* [Directory of Examples](https://pkg.go.dev/git.internal/re/jwt/v4#pkg-examples)
|
||||
|
||||
## Extensions
|
||||
|
||||
|
@ -96,7 +96,7 @@ A token is simply a JSON object that is signed by its author. this tells you exa
|
|||
* The author of the token was in the possession of the signing secret
|
||||
* The data has not been modified since it was signed
|
||||
|
||||
It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library.
|
||||
It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. The companion project https://github.com/golang-jwt/jwe aims at a (very) experimental implementation of the JWE standard.
|
||||
|
||||
### Choosing a Signing Method
|
||||
|
||||
|
@ -110,10 +110,10 @@ Asymmetric signing methods, such as RSA, use different keys for signing and veri
|
|||
|
||||
Each signing method expects a different object type for its signing keys. See the package documentation for details. Here are the most common ones:
|
||||
|
||||
* The [HMAC signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation
|
||||
* The [RSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation
|
||||
* The [ECDSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation
|
||||
* The [EdDSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodEd25519) (`Ed25519`) expect `ed25519.PrivateKey` for signing and `ed25519.PublicKey` for validation
|
||||
* The [HMAC signing method](https://pkg.go.dev/git.internal/re/jwt/v4#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation
|
||||
* The [RSA signing method](https://pkg.go.dev/git.internal/re/jwt/v4#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation
|
||||
* The [ECDSA signing method](https://pkg.go.dev/git.internal/re/jwt/v4#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation
|
||||
* The [EdDSA signing method](https://pkg.go.dev/git.internal/re/jwt/v4#SigningMethodEd25519) (`Ed25519`) expect `ed25519.PrivateKey` for signing and `ed25519.PublicKey` for validation
|
||||
|
||||
### JWT and OAuth
|
||||
|
||||
|
@ -131,7 +131,7 @@ This library uses descriptive error messages whenever possible. If you are not g
|
|||
|
||||
## More
|
||||
|
||||
Documentation can be found [on pkg.go.dev](https://pkg.go.dev/github.com/golang-jwt/jwt).
|
||||
Documentation can be found [on pkg.go.dev](https://pkg.go.dev/git.internal/re/jwt/v4).
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ As of February 2022 (and until this document is updated), the latest version `v4
|
|||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you think you found a vulnerability, and even if you are not sure, please report it to [@mfridman](http://github.com/mfridman) or one of the other [golang-jwt maintainers](https://github.com/orgs/golang-jwt/people). Please try be explicit, describe steps to reproduce the security issue with code example(s).
|
||||
If you think you found a vulnerability, and even if you are not sure, please report it to jwt-go-security@googlegroups.com or one of the other [golang-jwt maintainers](https://github.com/orgs/golang-jwt/people). Please try be explicit, describe steps to reproduce the security issue with code example(s).
|
||||
|
||||
You will receive a response within a timely manner. If the issue is confirmed, we will do our best to release a patch as soon as possible given the complexity of the problem.
|
||||
|
||||
|
|
138
claims.go
138
claims.go
|
@ -1,6 +1,7 @@
|
|||
package jwt
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
@ -43,65 +44,79 @@ type RegisteredClaims struct {
|
|||
ID string `json:"jti,omitempty"`
|
||||
}
|
||||
|
||||
func (c RegisteredClaims) GetExpiryAt() *NumericDate {
|
||||
return c.ExpiresAt
|
||||
}
|
||||
|
||||
func (c RegisteredClaims) GetNotBefore() *NumericDate {
|
||||
return c.NotBefore
|
||||
}
|
||||
|
||||
func (c RegisteredClaims) GetIssuedAt() *NumericDate {
|
||||
return c.IssuedAt
|
||||
}
|
||||
|
||||
func (c RegisteredClaims) GetAudience() ClaimStrings {
|
||||
return c.Audience
|
||||
}
|
||||
|
||||
func (c RegisteredClaims) GetIssuer() string {
|
||||
return c.Issuer
|
||||
}
|
||||
|
||||
// Valid validates time based claims "exp, iat, nbf".
|
||||
// There is no accounting for clock skew.
|
||||
// As well, if any of the above claims are not in the token, it will still
|
||||
// be considered a valid claim.
|
||||
//
|
||||
// Deprecated: This function should not be called directly, rather a claim should be validated using
|
||||
// the Validator struct.
|
||||
func (c RegisteredClaims) Valid() error {
|
||||
return NewValidator().Validate(c)
|
||||
vErr := new(ValidationError)
|
||||
now := TimeFunc()
|
||||
|
||||
// The claims below are optional, by default, so if they are set to the
|
||||
// default value in Go, let's not fail the verification for them.
|
||||
if !c.VerifyExpiresAt(now, false) {
|
||||
delta := now.Sub(c.ExpiresAt.Time)
|
||||
vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
|
||||
vErr.Errors |= ValidationErrorExpired
|
||||
}
|
||||
|
||||
if !c.VerifyIssuedAt(now, false) {
|
||||
vErr.Inner = ErrTokenUsedBeforeIssued
|
||||
vErr.Errors |= ValidationErrorIssuedAt
|
||||
}
|
||||
|
||||
if !c.VerifyNotBefore(now, false) {
|
||||
vErr.Inner = ErrTokenNotValidYet
|
||||
vErr.Errors |= ValidationErrorNotValidYet
|
||||
}
|
||||
|
||||
if vErr.valid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return vErr
|
||||
}
|
||||
|
||||
// VerifyAudience compares the aud claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *RegisteredClaims) VerifyAudience(cmp string, req bool) bool {
|
||||
return NewValidator().VerifyAudience(c, cmp, req)
|
||||
return verifyAud(c.Audience, cmp, req)
|
||||
}
|
||||
|
||||
// VerifyExpiresAt compares the exp claim against cmp (cmp < exp).
|
||||
// If req is false, it will return true, if exp is unset.
|
||||
func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool) bool {
|
||||
return NewValidator().VerifyExpiresAt(c, cmp, req)
|
||||
if c.ExpiresAt == nil {
|
||||
return verifyExp(nil, cmp, req)
|
||||
}
|
||||
|
||||
return verifyExp(&c.ExpiresAt.Time, cmp, req)
|
||||
}
|
||||
|
||||
// VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
|
||||
// If req is false, it will return true, if iat is unset.
|
||||
func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool) bool {
|
||||
return NewValidator().VerifyIssuedAt(c, cmp, req)
|
||||
if c.IssuedAt == nil {
|
||||
return verifyIat(nil, cmp, req)
|
||||
}
|
||||
|
||||
return verifyIat(&c.IssuedAt.Time, cmp, req)
|
||||
}
|
||||
|
||||
// VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf).
|
||||
// If req is false, it will return true, if nbf is unset.
|
||||
func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool) bool {
|
||||
return NewValidator().VerifyNotBefore(c, cmp, req)
|
||||
if c.NotBefore == nil {
|
||||
return verifyNbf(nil, cmp, req)
|
||||
}
|
||||
|
||||
return verifyNbf(&c.NotBefore.Time, cmp, req)
|
||||
}
|
||||
|
||||
// VerifyIssuer compares the iss claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *RegisteredClaims) VerifyIssuer(cmp string, req bool) bool {
|
||||
return NewValidator().VerifyIssuer(c, cmp, req)
|
||||
return verifyIss(c.Issuer, cmp, req)
|
||||
}
|
||||
|
||||
// StandardClaims are a structured version of the JWT Claims Set, as referenced at
|
||||
|
@ -165,33 +180,33 @@ func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool {
|
|||
// If req is false, it will return true, if exp is unset.
|
||||
func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool {
|
||||
if c.ExpiresAt == 0 {
|
||||
return verifyExp(nil, time.Unix(cmp, 0), req, 0)
|
||||
return verifyExp(nil, time.Unix(cmp, 0), req)
|
||||
}
|
||||
|
||||
t := time.Unix(c.ExpiresAt, 0)
|
||||
return verifyExp(&t, time.Unix(cmp, 0), req, 0)
|
||||
return verifyExp(&t, time.Unix(cmp, 0), req)
|
||||
}
|
||||
|
||||
// VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
|
||||
// If req is false, it will return true, if iat is unset.
|
||||
func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
|
||||
if c.IssuedAt == 0 {
|
||||
return verifyIat(nil, time.Unix(cmp, 0), req, 0)
|
||||
return verifyIat(nil, time.Unix(cmp, 0), req)
|
||||
}
|
||||
|
||||
t := time.Unix(c.IssuedAt, 0)
|
||||
return verifyIat(&t, time.Unix(cmp, 0), req, 0)
|
||||
return verifyIat(&t, time.Unix(cmp, 0), req)
|
||||
}
|
||||
|
||||
// VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf).
|
||||
// If req is false, it will return true, if nbf is unset.
|
||||
func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
|
||||
if c.NotBefore == 0 {
|
||||
return verifyNbf(nil, time.Unix(cmp, 0), req, 0)
|
||||
return verifyNbf(nil, time.Unix(cmp, 0), req)
|
||||
}
|
||||
|
||||
t := time.Unix(c.NotBefore, 0)
|
||||
return verifyNbf(&t, time.Unix(cmp, 0), req, 0)
|
||||
return verifyNbf(&t, time.Unix(cmp, 0), req)
|
||||
}
|
||||
|
||||
// VerifyIssuer compares the iss claim against cmp.
|
||||
|
@ -199,3 +214,56 @@ func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
|
|||
func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool {
|
||||
return verifyIss(c.Issuer, cmp, req)
|
||||
}
|
||||
|
||||
// ----- helpers
|
||||
|
||||
func verifyAud(aud []string, cmp string, required bool) bool {
|
||||
if len(aud) == 0 {
|
||||
return !required
|
||||
}
|
||||
// use a var here to keep constant time compare when looping over a number of claims
|
||||
result := false
|
||||
|
||||
var stringClaims string
|
||||
for _, a := range aud {
|
||||
if subtle.ConstantTimeCompare([]byte(a), []byte(cmp)) != 0 {
|
||||
result = true
|
||||
}
|
||||
stringClaims = stringClaims + a
|
||||
}
|
||||
|
||||
// case where "" is sent in one or many aud claims
|
||||
if len(stringClaims) == 0 {
|
||||
return !required
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func verifyExp(exp *time.Time, now time.Time, required bool) bool {
|
||||
if exp == nil {
|
||||
return !required
|
||||
}
|
||||
return now.Before(*exp)
|
||||
}
|
||||
|
||||
func verifyIat(iat *time.Time, now time.Time, required bool) bool {
|
||||
if iat == nil {
|
||||
return !required
|
||||
}
|
||||
return now.After(*iat) || now.Equal(*iat)
|
||||
}
|
||||
|
||||
func verifyNbf(nbf *time.Time, now time.Time, required bool) bool {
|
||||
if nbf == nil {
|
||||
return !required
|
||||
}
|
||||
return now.After(*nbf) || now.Equal(*nbf)
|
||||
}
|
||||
|
||||
func verifyIss(iss string, cmp string, required bool) bool {
|
||||
if iss == "" {
|
||||
return !required
|
||||
}
|
||||
return subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0
|
||||
}
|
||||
|
|
|
@ -16,4 +16,4 @@ To simply display a token, use:
|
|||
|
||||
You can install this tool with the following command:
|
||||
|
||||
go install github.com/golang-jwt/jwt/v4/cmd/jwt
|
||||
go install git.internal/re/jwt/v4/cmd/jwt
|
|
@ -3,7 +3,8 @@
|
|||
//
|
||||
// Example usage:
|
||||
// The following will create and sign a token, then verify it and output the original claims.
|
||||
// echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify -
|
||||
//
|
||||
// echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify -
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -16,7 +17,7 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
var ecdsaTestData = []struct {
|
||||
|
@ -90,7 +90,6 @@ func TestECDSASign(t *testing.T) {
|
|||
toSign := strings.Join(parts[0:2], ".")
|
||||
method := jwt.GetSigningMethod(data.alg)
|
||||
sig, err := method.Sign(toSign, ecdsaKey)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("[%v] Error signing token: %v", data.name, err)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
var ed25519TestData = []struct {
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
// Example (atypical) using the RegisteredClaims type by itself to parse a token.
|
||||
|
@ -25,7 +25,7 @@ func ExampleNewWithClaims_registeredClaims() {
|
|||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
ss, err := token.SignedString(mySigningKey)
|
||||
fmt.Printf("%v %v", ss, err)
|
||||
//Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.0XN_1Tpp9FszFOonIBpwha0c_SfnNI22DhTnjMshPg8 <nil>
|
||||
// Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.0XN_1Tpp9FszFOonIBpwha0c_SfnNI22DhTnjMshPg8 <nil>
|
||||
}
|
||||
|
||||
// Example creating a token using a custom claims type. The RegisteredClaims is embedded
|
||||
|
@ -67,7 +67,7 @@ func ExampleNewWithClaims_customClaimsType() {
|
|||
ss, err := token.SignedString(mySigningKey)
|
||||
fmt.Printf("%v %v", ss, err)
|
||||
|
||||
//Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.xVuY2FZ_MRXMIEgVQ7J-TFtaucVFRXUzHm9LmV41goM <nil>
|
||||
// Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.xVuY2FZ_MRXMIEgVQ7J-TFtaucVFRXUzHm9LmV41goM <nil>
|
||||
}
|
||||
|
||||
// Example creating a token using a custom claims type. The StandardClaim is embedded
|
||||
|
@ -96,7 +96,7 @@ func ExampleParseWithClaims_customClaimsType() {
|
|||
// An example of parsing the error types using bitfield checks
|
||||
func ExampleParse_errorChecking() {
|
||||
// Token from another example. This token is expired
|
||||
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
|
||||
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
|
||||
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte("AllYourBase"), nil
|
||||
|
|
2
go.mod
2
go.mod
|
@ -1,4 +1,4 @@
|
|||
module github.com/golang-jwt/jwt/v4
|
||||
module git.internal/re/jwt/v4
|
||||
|
||||
go 1.16
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
// For HMAC signing method, the key can be any []byte. It is recommended to generate
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
var hmacTestData = []struct {
|
||||
|
|
|
@ -16,8 +16,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4/request"
|
||||
"git.internal/re/jwt/v4"
|
||||
"git.internal/re/jwt/v4/request"
|
||||
)
|
||||
|
||||
// location of the files used for signing and verification
|
||||
|
@ -73,7 +73,7 @@ type CustomerInfo struct {
|
|||
}
|
||||
|
||||
type CustomClaimsExample struct {
|
||||
*jwt.RegisteredClaims
|
||||
jwt.RegisteredClaims
|
||||
TokenType string
|
||||
CustomerInfo
|
||||
}
|
||||
|
@ -109,11 +109,10 @@ func Example_getTokenViaHTTP() {
|
|||
claims := token.Claims.(*CustomClaimsExample)
|
||||
fmt.Println(claims.CustomerInfo.Name)
|
||||
|
||||
//Output: test
|
||||
// Output: test
|
||||
}
|
||||
|
||||
func Example_useTokenViaHTTP() {
|
||||
|
||||
// Make a sample token
|
||||
// In a real world situation, this token will have been acquired from
|
||||
// some other API call (see Example_getTokenViaHTTP)
|
||||
|
@ -142,7 +141,7 @@ func createToken(user string) (string, error) {
|
|||
|
||||
// set our claims
|
||||
t.Claims = &CustomClaimsExample{
|
||||
&jwt.RegisteredClaims{
|
||||
jwt.RegisteredClaims{
|
||||
// set the expire time
|
||||
// see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 1)),
|
||||
|
@ -197,7 +196,6 @@ func restrictedHandler(w http.ResponseWriter, r *http.Request) {
|
|||
// we also only use its public counter part to verify
|
||||
return verifyKey, nil
|
||||
}, request.WithClaims(&CustomClaimsExample{}))
|
||||
|
||||
// If the token is missing or invalid, return error
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
|
|
|
@ -45,14 +45,14 @@ func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool {
|
|||
switch exp := v.(type) {
|
||||
case float64:
|
||||
if exp == 0 {
|
||||
return verifyExp(nil, cmpTime, req, 0)
|
||||
return verifyExp(nil, cmpTime, req)
|
||||
}
|
||||
|
||||
return verifyExp(&newNumericDateFromSeconds(exp).Time, cmpTime, req, 0)
|
||||
return verifyExp(&newNumericDateFromSeconds(exp).Time, cmpTime, req)
|
||||
case json.Number:
|
||||
v, _ := exp.Float64()
|
||||
|
||||
return verifyExp(&newNumericDateFromSeconds(v).Time, cmpTime, req, 0)
|
||||
return verifyExp(&newNumericDateFromSeconds(v).Time, cmpTime, req)
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -71,14 +71,14 @@ func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool {
|
|||
switch iat := v.(type) {
|
||||
case float64:
|
||||
if iat == 0 {
|
||||
return verifyIat(nil, cmpTime, req, 0)
|
||||
return verifyIat(nil, cmpTime, req)
|
||||
}
|
||||
|
||||
return verifyIat(&newNumericDateFromSeconds(iat).Time, cmpTime, req, 0)
|
||||
return verifyIat(&newNumericDateFromSeconds(iat).Time, cmpTime, req)
|
||||
case json.Number:
|
||||
v, _ := iat.Float64()
|
||||
|
||||
return verifyIat(&newNumericDateFromSeconds(v).Time, cmpTime, req, 0)
|
||||
return verifyIat(&newNumericDateFromSeconds(v).Time, cmpTime, req)
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -97,14 +97,14 @@ func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool {
|
|||
switch nbf := v.(type) {
|
||||
case float64:
|
||||
if nbf == 0 {
|
||||
return verifyNbf(nil, cmpTime, req, 0)
|
||||
return verifyNbf(nil, cmpTime, req)
|
||||
}
|
||||
|
||||
return verifyNbf(&newNumericDateFromSeconds(nbf).Time, cmpTime, req, 0)
|
||||
return verifyNbf(&newNumericDateFromSeconds(nbf).Time, cmpTime, req)
|
||||
case json.Number:
|
||||
v, _ := nbf.Float64()
|
||||
|
||||
return verifyNbf(&newNumericDateFromSeconds(v).Time, cmpTime, req, 0)
|
||||
return verifyNbf(&newNumericDateFromSeconds(v).Time, cmpTime, req)
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
var noneTestData = []struct {
|
||||
|
|
48
parser.go
48
parser.go
|
@ -22,16 +22,11 @@ type Parser struct {
|
|||
//
|
||||
// Deprecated: In future releases, this field will not be exported anymore and should be set with an option to NewParser instead.
|
||||
SkipClaimsValidation bool
|
||||
|
||||
validator *Validator
|
||||
}
|
||||
|
||||
// NewParser creates a new Parser with the specified options
|
||||
func NewParser(options ...ParserOption) *Parser {
|
||||
p := &Parser{
|
||||
// Supply a default validator
|
||||
validator: NewValidator(),
|
||||
}
|
||||
p := &Parser{}
|
||||
|
||||
// loop through our parsing options and apply them
|
||||
for _, option := range options {
|
||||
|
@ -47,6 +42,13 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
|
|||
return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
|
||||
}
|
||||
|
||||
// ParseWithClaims parses, validates, and verifies like Parse, but supplies a default object implementing the Claims
|
||||
// interface. This provides default values which can be overridden and allows a caller to use their own type, rather
|
||||
// than the default MapClaims implementation of Claims.
|
||||
//
|
||||
// Note: If you provide a custom claim implementation that embeds one of the standard claims (such as RegisteredClaims),
|
||||
// make sure that a) you either embed a non-pointer version of the claims or b) if you are using a pointer, allocate the
|
||||
// proper memory for it before passing in the overall claims, otherwise you might run into a panic.
|
||||
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
|
||||
token, parts, err := p.ParseUnverified(tokenString, claims)
|
||||
if err != nil {
|
||||
|
@ -87,34 +89,14 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
|
|||
|
||||
// Validate Claims
|
||||
if !p.SkipClaimsValidation {
|
||||
// Experimental. It gets pretty messy here, because we have a new
|
||||
// interface, that not all Claims (especially ones external to the
|
||||
// package) might implement.
|
||||
if claimsv2, ok := token.Claims.(ClaimsV2); ok {
|
||||
// Make sure we have at least a default validator
|
||||
if p.validator == nil {
|
||||
p.validator = NewValidator()
|
||||
}
|
||||
if err := token.Claims.Valid(); err != nil {
|
||||
|
||||
if err := p.validator.Validate(claimsv2); err != nil {
|
||||
// If the Claims Valid returned an error, check if it is a validation error,
|
||||
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
|
||||
if e, ok := err.(*ValidationError); !ok {
|
||||
vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
|
||||
} else {
|
||||
vErr = e
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Legacy way of validating
|
||||
if err := token.Claims.Valid(); err != nil {
|
||||
// If the Claims Valid returned an error, check if it is a validation error,
|
||||
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
|
||||
if e, ok := err.(*ValidationError); !ok {
|
||||
vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
|
||||
} else {
|
||||
vErr = e
|
||||
}
|
||||
// If the Claims Valid returned an error, check if it is a validation error,
|
||||
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
|
||||
if e, ok := err.(*ValidationError); !ok {
|
||||
vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
|
||||
} else {
|
||||
vErr = e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,9 +27,3 @@ func WithoutClaimsValidation() ParserOption {
|
|||
p.SkipClaimsValidation = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithValidator(v *Validator) ParserOption {
|
||||
return func(p *Parser) {
|
||||
p.validator = v
|
||||
}
|
||||
}
|
||||
|
|
143
parser_test.go
143
parser_test.go
|
@ -3,14 +3,15 @@ package jwt_test
|
|||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4/test"
|
||||
"git.internal/re/jwt/v4"
|
||||
"git.internal/re/jwt/v4/test"
|
||||
)
|
||||
|
||||
var errKeyFuncError error = fmt.Errorf("error loading key")
|
||||
|
@ -41,7 +42,6 @@ func init() {
|
|||
// Load private keys
|
||||
jwtTestRSAPrivateKey = test.LoadRSAPrivateKeyFromDisk("test/sample_key")
|
||||
jwtTestEC256PrivateKey = test.LoadECPrivateKeyFromDisk("test/ec256-private.pem")
|
||||
|
||||
}
|
||||
|
||||
var jwtTestData = []struct {
|
||||
|
@ -55,7 +55,7 @@ var jwtTestData = []struct {
|
|||
parser *jwt.Parser
|
||||
signingMethod jwt.SigningMethod // The method to sign the JWT token for test purpose
|
||||
}{
|
||||
/*{
|
||||
{
|
||||
"basic",
|
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
|
||||
defaultKeyFunc,
|
||||
|
@ -320,28 +320,6 @@ var jwtTestData = []struct {
|
|||
&jwt.Parser{UseJSONNumber: true},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
{
|
||||
"RFC7519 Claims - nbf with 60s skew",
|
||||
"", // autogen
|
||||
defaultKeyFunc,
|
||||
&jwt.RegisteredClaims{NotBefore: jwt.NewNumericDate(time.Now().Add(time.Second * 100))},
|
||||
false,
|
||||
jwt.ValidationErrorNotValidYet,
|
||||
[]error{jwt.ErrTokenNotValidYet},
|
||||
jwt.NewParser(jwt.WithValidator(jwt.NewValidator(jwt.WithLeeway(time.Minute)))),
|
||||
jwt.SigningMethodRS256,
|
||||
},*/
|
||||
{
|
||||
"RFC7519 Claims - nbf with 120s skew",
|
||||
"", // autogen
|
||||
defaultKeyFunc,
|
||||
&jwt.RegisteredClaims{NotBefore: jwt.NewNumericDate(time.Now().Add(time.Second * 100))},
|
||||
true,
|
||||
0,
|
||||
nil,
|
||||
jwt.NewParser(jwt.WithValidator(jwt.NewValidator(jwt.WithLeeway(2 * time.Minute)))),
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
}
|
||||
|
||||
// signToken creates and returns a signed JWT token using signingMethod.
|
||||
|
@ -359,11 +337,9 @@ func signToken(claims jwt.Claims, signingMethod jwt.SigningMethod) string {
|
|||
}
|
||||
|
||||
func TestParser_Parse(t *testing.T) {
|
||||
|
||||
// Iterate over test data set and run tests
|
||||
for _, data := range jwtTestData {
|
||||
t.Run(data.name, func(t *testing.T) {
|
||||
|
||||
// If the token string is blank, use helper function to generate string
|
||||
if data.tokenString == "" {
|
||||
data.tokenString = signToken(data.claims, data.signingMethod)
|
||||
|
@ -373,9 +349,9 @@ func TestParser_Parse(t *testing.T) {
|
|||
var token *jwt.Token
|
||||
var ve *jwt.ValidationError
|
||||
var err error
|
||||
var parser = data.parser
|
||||
parser := data.parser
|
||||
if parser == nil {
|
||||
parser = jwt.NewParser()
|
||||
parser = new(jwt.Parser)
|
||||
}
|
||||
// Figure out correct claims type
|
||||
switch data.claims.(type) {
|
||||
|
@ -425,7 +401,7 @@ func TestParser_Parse(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Errorf("[%v] Expecting error(s). Didn't get one.", data.name)
|
||||
} else {
|
||||
var all = false
|
||||
all := false
|
||||
for _, e := range data.err {
|
||||
all = errors.Is(err, e)
|
||||
}
|
||||
|
@ -450,7 +426,6 @@ func TestParser_Parse(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParser_ParseUnverified(t *testing.T) {
|
||||
|
||||
// Iterate over test data set and run tests
|
||||
for _, data := range jwtTestData {
|
||||
// Skip test data, that intentionally contains malformed tokens, as they would lead to an error
|
||||
|
@ -467,7 +442,7 @@ func TestParser_ParseUnverified(t *testing.T) {
|
|||
// Parse the token
|
||||
var token *jwt.Token
|
||||
var err error
|
||||
var parser = data.parser
|
||||
parser := data.parser
|
||||
if parser == nil {
|
||||
parser = new(jwt.Parser)
|
||||
}
|
||||
|
@ -510,6 +485,7 @@ var setPaddingTestData = []struct {
|
|||
tokenString string
|
||||
claims jwt.Claims
|
||||
paddedDecode bool
|
||||
strictDecode bool
|
||||
signingMethod jwt.SigningMethod
|
||||
keyfunc jwt.Keyfunc
|
||||
valid bool
|
||||
|
@ -568,19 +544,108 @@ var setPaddingTestData = []struct {
|
|||
keyfunc: paddedKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
// DecodeStrict tests, DecodePaddingAllowed=false
|
||||
{
|
||||
name: "Validated non-padded token with padding disabled, non-strict decode, non-tweaked signature",
|
||||
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ.bI15h-7mN0f-2diX5I4ErgNQy1uM-rJS5Sz7O0iTWtWSBxY1h6wy8Ywxe5EZTEO6GiIfk7Lk-72Ex-c5aA40QKhPwWB9BJ8O_LfKpezUVBOn0jRItDnVdsk4ccl2zsOVkbA4U4QvdrSbOYMbwoRHzDXfTFpoeMWtn3ez0aENJ8dh4E1echHp5ByI9Pu2aBsvM1WVcMt_BySweCL3f4T7jNZeXDr7Txd00yUd2gdsHYPjXorOvsgaBKN5GLsWd1zIY5z-2gCC8CRSN-IJ4NNX5ifh7l-bOXE2q7szTqa9pvyE9y6TQJhNMSE2FotRce_TOPBWgGpQ-K2I7E8x7wZ8O" +
|
||||
"g",
|
||||
claims: nil,
|
||||
paddedDecode: false,
|
||||
strictDecode: false,
|
||||
signingMethod: jwt.SigningMethodRS256,
|
||||
keyfunc: defaultKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Validated non-padded token with padding disabled, non-strict decode, tweaked signature",
|
||||
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ.bI15h-7mN0f-2diX5I4ErgNQy1uM-rJS5Sz7O0iTWtWSBxY1h6wy8Ywxe5EZTEO6GiIfk7Lk-72Ex-c5aA40QKhPwWB9BJ8O_LfKpezUVBOn0jRItDnVdsk4ccl2zsOVkbA4U4QvdrSbOYMbwoRHzDXfTFpoeMWtn3ez0aENJ8dh4E1echHp5ByI9Pu2aBsvM1WVcMt_BySweCL3f4T7jNZeXDr7Txd00yUd2gdsHYPjXorOvsgaBKN5GLsWd1zIY5z-2gCC8CRSN-IJ4NNX5ifh7l-bOXE2q7szTqa9pvyE9y6TQJhNMSE2FotRce_TOPBWgGpQ-K2I7E8x7wZ8O" +
|
||||
"h",
|
||||
claims: nil,
|
||||
paddedDecode: false,
|
||||
strictDecode: false,
|
||||
signingMethod: jwt.SigningMethodRS256,
|
||||
keyfunc: defaultKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Validated non-padded token with padding disabled, strict decode, non-tweaked signature",
|
||||
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ.bI15h-7mN0f-2diX5I4ErgNQy1uM-rJS5Sz7O0iTWtWSBxY1h6wy8Ywxe5EZTEO6GiIfk7Lk-72Ex-c5aA40QKhPwWB9BJ8O_LfKpezUVBOn0jRItDnVdsk4ccl2zsOVkbA4U4QvdrSbOYMbwoRHzDXfTFpoeMWtn3ez0aENJ8dh4E1echHp5ByI9Pu2aBsvM1WVcMt_BySweCL3f4T7jNZeXDr7Txd00yUd2gdsHYPjXorOvsgaBKN5GLsWd1zIY5z-2gCC8CRSN-IJ4NNX5ifh7l-bOXE2q7szTqa9pvyE9y6TQJhNMSE2FotRce_TOPBWgGpQ-K2I7E8x7wZ8O" +
|
||||
"g",
|
||||
claims: nil,
|
||||
paddedDecode: false,
|
||||
strictDecode: true,
|
||||
signingMethod: jwt.SigningMethodRS256,
|
||||
keyfunc: defaultKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Error for non-padded token with padding disabled, strict decode, tweaked signature",
|
||||
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ.bI15h-7mN0f-2diX5I4ErgNQy1uM-rJS5Sz7O0iTWtWSBxY1h6wy8Ywxe5EZTEO6GiIfk7Lk-72Ex-c5aA40QKhPwWB9BJ8O_LfKpezUVBOn0jRItDnVdsk4ccl2zsOVkbA4U4QvdrSbOYMbwoRHzDXfTFpoeMWtn3ez0aENJ8dh4E1echHp5ByI9Pu2aBsvM1WVcMt_BySweCL3f4T7jNZeXDr7Txd00yUd2gdsHYPjXorOvsgaBKN5GLsWd1zIY5z-2gCC8CRSN-IJ4NNX5ifh7l-bOXE2q7szTqa9pvyE9y6TQJhNMSE2FotRce_TOPBWgGpQ-K2I7E8x7wZ8O" +
|
||||
"h",
|
||||
claims: nil,
|
||||
paddedDecode: false,
|
||||
strictDecode: true,
|
||||
signingMethod: jwt.SigningMethodRS256,
|
||||
keyfunc: defaultKeyFunc,
|
||||
valid: false,
|
||||
},
|
||||
// DecodeStrict tests, DecodePaddingAllowed=true
|
||||
{
|
||||
name: "Validated padded token with padding enabled, non-strict decode, non-tweaked signature",
|
||||
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3gr" +
|
||||
"w==",
|
||||
claims: nil,
|
||||
paddedDecode: true,
|
||||
strictDecode: false,
|
||||
signingMethod: jwt.SigningMethodES256,
|
||||
keyfunc: paddedKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Validated padded token with padding enabled, non-strict decode, tweaked signature",
|
||||
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3gr" +
|
||||
"x==",
|
||||
claims: nil,
|
||||
paddedDecode: true,
|
||||
strictDecode: false,
|
||||
signingMethod: jwt.SigningMethodES256,
|
||||
keyfunc: paddedKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Validated padded token with padding enabled, strict decode, non-tweaked signature",
|
||||
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3gr" +
|
||||
"w==",
|
||||
claims: nil,
|
||||
paddedDecode: true,
|
||||
strictDecode: true,
|
||||
signingMethod: jwt.SigningMethodES256,
|
||||
keyfunc: paddedKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Error for padded token with padding enabled, strict decode, tweaked signature",
|
||||
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3gr" +
|
||||
"x==",
|
||||
claims: nil,
|
||||
paddedDecode: true,
|
||||
strictDecode: true,
|
||||
signingMethod: jwt.SigningMethodES256,
|
||||
keyfunc: paddedKeyFunc,
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Extension of Parsing, this is to test out functionality specific to switching codecs with padding.
|
||||
func TestSetPadding(t *testing.T) {
|
||||
for _, data := range setPaddingTestData {
|
||||
t.Run(data.name, func(t *testing.T) {
|
||||
jwt.DecodePaddingAllowed = data.paddedDecode
|
||||
jwt.DecodeStrict = data.strictDecode
|
||||
|
||||
// If the token string is blank, use helper function to generate string
|
||||
jwt.DecodePaddingAllowed = data.paddedDecode
|
||||
|
||||
if data.tokenString == "" {
|
||||
data.tokenString = signToken(data.claims, data.signingMethod)
|
||||
|
||||
}
|
||||
|
||||
// Parse the token
|
||||
|
@ -599,15 +664,13 @@ func TestSetPadding(t *testing.T) {
|
|||
err,
|
||||
)
|
||||
}
|
||||
|
||||
})
|
||||
jwt.DecodePaddingAllowed = false
|
||||
|
||||
jwt.DecodeStrict = false
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParseUnverified(b *testing.B) {
|
||||
|
||||
// Iterate over test data set and run tests
|
||||
for _, data := range jwtTestData {
|
||||
// If the token string is blank, use helper function to generate string
|
||||
|
@ -616,7 +679,7 @@ func BenchmarkParseUnverified(b *testing.B) {
|
|||
}
|
||||
|
||||
// Parse the token
|
||||
var parser = data.parser
|
||||
parser := data.parser
|
||||
if parser == nil {
|
||||
parser = new(jwt.Parser)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package request
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Errors
|
||||
|
@ -79,3 +80,18 @@ func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// BearerExtractor extracts a token from the Authorization header.
|
||||
// The header is expected to match the format "Bearer XX", where "XX" is the
|
||||
// JWT token.
|
||||
type BearerExtractor struct{}
|
||||
|
||||
func (e BearerExtractor) ExtractToken(req *http.Request) (string, error) {
|
||||
tokenHeader := req.Header.Get("Authorization")
|
||||
// The usual convention is for "Bearer" to be title-cased. However, there's no
|
||||
// strict rule around this, and it's best to follow the robustness principle here.
|
||||
if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), "bearer ") {
|
||||
return "", ErrNoTokenInRequest
|
||||
}
|
||||
return tokenHeader[7:], nil
|
||||
}
|
||||
|
|
|
@ -89,3 +89,23 @@ func makeExampleRequest(method, path string, headers map[string]string, urlArgs
|
|||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func TestBearerExtractor(t *testing.T) {
|
||||
request := makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "Bearer ToKen"}, nil)
|
||||
token, err := BearerExtractor{}.ExtractToken(request)
|
||||
if err != nil || token != "ToKen" {
|
||||
t.Errorf("ExtractToken did not return token, returned: %v, %v", token, err)
|
||||
}
|
||||
|
||||
request = makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "Bearo ToKen"}, nil)
|
||||
token, err = BearerExtractor{}.ExtractToken(request)
|
||||
if err == nil || token != "" {
|
||||
t.Errorf("ExtractToken did not return error, returned: %v, %v", token, err)
|
||||
}
|
||||
|
||||
request = makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "BeArEr HeLO"}, nil)
|
||||
token, err = BearerExtractor{}.ExtractToken(request)
|
||||
if err != nil || token != "HeLO" {
|
||||
t.Errorf("ExtractToken did not return token, returned: %v, %v", token, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package request
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
// ParseFromRequest extracts and parses a JWT token from an HTTP request.
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4/test"
|
||||
"git.internal/re/jwt/v4"
|
||||
"git.internal/re/jwt/v4/test"
|
||||
)
|
||||
|
||||
var requestTestData = []struct {
|
||||
|
|
|
@ -10,8 +10,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4/test"
|
||||
"git.internal/re/jwt/v4"
|
||||
"git.internal/re/jwt/v4/test"
|
||||
)
|
||||
|
||||
var rsaPSSTestData = []struct {
|
||||
|
|
|
@ -5,42 +5,37 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
var rsaTestData = []struct {
|
||||
name string
|
||||
tokenString string
|
||||
alg string
|
||||
claims map[string]interface{}
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
"Basic RS256",
|
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
|
||||
"RS256",
|
||||
map[string]interface{}{"foo": "bar"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Basic RS384",
|
||||
"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw",
|
||||
"RS384",
|
||||
map[string]interface{}{"foo": "bar"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Basic RS512",
|
||||
"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ",
|
||||
"RS512",
|
||||
map[string]interface{}{"foo": "bar"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"basic invalid: foo => bar",
|
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
|
||||
"RS256",
|
||||
map[string]interface{}{"foo": "bar"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
@ -152,7 +147,6 @@ func TestRSAKeyParsing(t *testing.T) {
|
|||
if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil {
|
||||
t.Errorf("Parsed invalid key as valid private key: %v", k)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkRSAParsing(b *testing.B) {
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"crypto/rsa"
|
||||
"os"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
func LoadRSAPrivateKeyFromDisk(location string) *rsa.PrivateKey {
|
||||
|
|
20
token.go
20
token.go
|
@ -14,6 +14,12 @@ import (
|
|||
// To use the non-recommended decoding, set this boolean to `true` prior to using this package.
|
||||
var DecodePaddingAllowed bool
|
||||
|
||||
// DecodeStrict will switch the codec used for decoding JWTs into strict mode.
|
||||
// In this mode, the decoder requires that trailing padding bits are zero, as described in RFC 4648 section 3.5.
|
||||
// Note that this is a global variable, and updating it will change the behavior on a package level, and is also NOT go-routine safe.
|
||||
// To use strict decoding, set this boolean to `true` prior to using this package.
|
||||
var DecodeStrict bool
|
||||
|
||||
// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
|
||||
// You can override it to use another time value. This is useful for testing or if your
|
||||
// server uses a different time zone than your tokens.
|
||||
|
@ -99,6 +105,11 @@ func Parse(tokenString string, keyFunc Keyfunc, options ...ParserOption) (*Token
|
|||
return NewParser(options...).Parse(tokenString, keyFunc)
|
||||
}
|
||||
|
||||
// ParseWithClaims is a shortcut for NewParser().ParseWithClaims().
|
||||
//
|
||||
// Note: If you provide a custom claim implementation that embeds one of the standard claims (such as RegisteredClaims),
|
||||
// make sure that a) you either embed a non-pointer version of the claims or b) if you are using a pointer, allocate the
|
||||
// proper memory for it before passing in the overall claims, otherwise you might run into a panic.
|
||||
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc, options ...ParserOption) (*Token, error) {
|
||||
return NewParser(options...).ParseWithClaims(tokenString, claims, keyFunc)
|
||||
}
|
||||
|
@ -116,12 +127,17 @@ func EncodeSegment(seg []byte) string {
|
|||
// Deprecated: In a future release, we will demote this function to a non-exported function, since it
|
||||
// should only be used internally
|
||||
func DecodeSegment(seg string) ([]byte, error) {
|
||||
encoding := base64.RawURLEncoding
|
||||
|
||||
if DecodePaddingAllowed {
|
||||
if l := len(seg) % 4; l > 0 {
|
||||
seg += strings.Repeat("=", 4-l)
|
||||
}
|
||||
return base64.URLEncoding.DecodeString(seg)
|
||||
encoding = base64.URLEncoding
|
||||
}
|
||||
|
||||
return base64.RawURLEncoding.DecodeString(seg)
|
||||
if DecodeStrict {
|
||||
encoding = encoding.Strict()
|
||||
}
|
||||
return encoding.DecodeString(seg)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package jwt_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
func TestToken_SigningString(t1 *testing.T) {
|
||||
|
|
18
types.go
18
types.go
|
@ -53,9 +53,23 @@ func (date NumericDate) MarshalJSON() (b []byte, err error) {
|
|||
if TimePrecision < time.Second {
|
||||
prec = int(math.Log10(float64(time.Second) / float64(TimePrecision)))
|
||||
}
|
||||
f := float64(date.Truncate(TimePrecision).UnixNano()) / float64(time.Second)
|
||||
truncatedDate := date.Truncate(TimePrecision)
|
||||
|
||||
return []byte(strconv.FormatFloat(f, 'f', prec, 64)), nil
|
||||
// For very large timestamps, UnixNano would overflow an int64, but this
|
||||
// function requires nanosecond level precision, so we have to use the
|
||||
// following technique to get round the issue:
|
||||
// 1. Take the normal unix timestamp to form the whole number part of the
|
||||
// output,
|
||||
// 2. Take the result of the Nanosecond function, which retuns the offset
|
||||
// within the second of the particular unix time instance, to form the
|
||||
// decimal part of the output
|
||||
// 3. Concatenate them to produce the final result
|
||||
seconds := strconv.FormatInt(truncatedDate.Unix(), 10)
|
||||
nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64)
|
||||
|
||||
output := append([]byte(seconds), []byte(nanosecondsOffset)[1:]...)
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON is an implementation of the json.RawMessage interface and deserializses a
|
||||
|
|
|
@ -2,10 +2,11 @@ package jwt_test
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
func TestNumericDate(t *testing.T) {
|
||||
|
@ -40,7 +41,6 @@ func TestSingleArrayMarshal(t *testing.T) {
|
|||
expected := `"test"`
|
||||
|
||||
b, err := json.Marshal(s)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %s", err)
|
||||
}
|
||||
|
@ -79,23 +79,38 @@ func TestNumericDate_MarshalJSON(t *testing.T) {
|
|||
}{
|
||||
{time.Unix(5243700879, 0), "5243700879", time.Second},
|
||||
{time.Unix(5243700879, 0), "5243700879.000", time.Millisecond},
|
||||
{time.Unix(5243700879, 0), "5243700879.000001", time.Microsecond},
|
||||
{time.Unix(5243700879, 0), "5243700879.000000954", time.Nanosecond},
|
||||
{time.Unix(5243700879, 0), "5243700879.000000", time.Microsecond},
|
||||
{time.Unix(5243700879, 0), "5243700879.000000000", time.Nanosecond},
|
||||
//
|
||||
{time.Unix(4239425898, 0), "4239425898", time.Second},
|
||||
{time.Unix(4239425898, 0), "4239425898.000", time.Millisecond},
|
||||
{time.Unix(4239425898, 0), "4239425898.000000", time.Microsecond},
|
||||
{time.Unix(4239425898, 0), "4239425898.000000000", time.Nanosecond},
|
||||
//
|
||||
{time.Unix(253402271999, 0), "253402271999", time.Second},
|
||||
{time.Unix(253402271999, 0), "253402271999.000", time.Millisecond},
|
||||
{time.Unix(253402271999, 0), "253402271999.000000", time.Microsecond},
|
||||
{time.Unix(253402271999, 0), "253402271999.000000000", time.Nanosecond},
|
||||
//
|
||||
{time.Unix(0, 1644285000210402000), "1644285000", time.Second},
|
||||
{time.Unix(0, 1644285000210402000), "1644285000.210", time.Millisecond},
|
||||
{time.Unix(0, 1644285000210402000), "1644285000.210402", time.Microsecond},
|
||||
{time.Unix(0, 1644285000210402000), "1644285000.210402012", time.Nanosecond},
|
||||
{time.Unix(0, 1644285000210402000), "1644285000.210402000", time.Nanosecond},
|
||||
//
|
||||
{time.Unix(0, 1644285315063096000), "1644285315", time.Second},
|
||||
{time.Unix(0, 1644285315063096000), "1644285315.063", time.Millisecond},
|
||||
{time.Unix(0, 1644285315063096000), "1644285315.063096", time.Microsecond},
|
||||
{time.Unix(0, 1644285315063096000), "1644285315.063096046", time.Nanosecond},
|
||||
{time.Unix(0, 1644285315063096000), "1644285315.063096000", time.Nanosecond},
|
||||
// Maximum time that a go time.Time can represent
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807", time.Second},
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807.999", time.Millisecond},
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807.999999", time.Microsecond},
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807.999999999", time.Nanosecond},
|
||||
// Strange precisions
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807", time.Second},
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854775756", time.Minute},
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854774016", time.Hour},
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854745216", 24 * time.Hour},
|
||||
}
|
||||
|
||||
for i, tc := range tt {
|
||||
|
|
164
validator.go
164
validator.go
|
@ -1,164 +0,0 @@
|
|||
package jwt
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Validator struct {
|
||||
leeway time.Duration
|
||||
}
|
||||
|
||||
type ClaimsV2 interface {
|
||||
GetExpiryAt() *NumericDate
|
||||
GetIssuedAt() *NumericDate
|
||||
GetNotBefore() *NumericDate
|
||||
GetIssuer() string
|
||||
GetAudience() ClaimStrings
|
||||
}
|
||||
|
||||
func (v *Validator) Validate(claims ClaimsV2) error {
|
||||
vErr := new(ValidationError)
|
||||
now := TimeFunc()
|
||||
|
||||
if !v.VerifyExpiresAt(claims, now, false) {
|
||||
exp := claims.GetExpiryAt()
|
||||
delta := now.Sub(exp.Time)
|
||||
vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
|
||||
vErr.Errors |= ValidationErrorExpired
|
||||
}
|
||||
|
||||
if !v.VerifyIssuedAt(claims, now, false) {
|
||||
vErr.Inner = ErrTokenUsedBeforeIssued
|
||||
vErr.Errors |= ValidationErrorIssuedAt
|
||||
}
|
||||
|
||||
if !v.VerifyNotBefore(claims, now, false) {
|
||||
vErr.Inner = ErrTokenNotValidYet
|
||||
vErr.Errors |= ValidationErrorNotValidYet
|
||||
}
|
||||
|
||||
if vErr.valid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return vErr
|
||||
}
|
||||
|
||||
// VerifyAudience compares the aud claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (v *Validator) VerifyAudience(claims ClaimsV2, cmp string, req bool) bool {
|
||||
return verifyAud(claims.GetAudience(), cmp, req)
|
||||
}
|
||||
|
||||
// VerifyExpiresAt compares the exp claim against cmp (cmp < exp).
|
||||
// If req is false, it will return true, if exp is unset.
|
||||
func (v *Validator) VerifyExpiresAt(claims ClaimsV2, cmp time.Time, req bool) bool {
|
||||
exp := claims.GetExpiryAt()
|
||||
if exp == nil {
|
||||
return verifyExp(nil, cmp, req, v.leeway)
|
||||
}
|
||||
|
||||
return verifyExp(&exp.Time, cmp, req, v.leeway)
|
||||
}
|
||||
|
||||
// VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
|
||||
// If req is false, it will return true, if iat is unset.
|
||||
func (v *Validator) VerifyIssuedAt(claims ClaimsV2, cmp time.Time, req bool) bool {
|
||||
iat := claims.GetIssuedAt()
|
||||
if iat == nil {
|
||||
return verifyIat(nil, cmp, req, v.leeway)
|
||||
}
|
||||
|
||||
return verifyIat(&iat.Time, cmp, req, v.leeway)
|
||||
}
|
||||
|
||||
// VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf).
|
||||
// If req is false, it will return true, if nbf is unset.
|
||||
func (v *Validator) VerifyNotBefore(claims ClaimsV2, cmp time.Time, req bool) bool {
|
||||
nbf := claims.GetNotBefore()
|
||||
if nbf == nil {
|
||||
return verifyNbf(nil, cmp, req, v.leeway)
|
||||
}
|
||||
|
||||
return verifyNbf(&nbf.Time, cmp, req, v.leeway)
|
||||
}
|
||||
|
||||
// VerifyIssuer compares the iss claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (v *Validator) VerifyIssuer(claims ClaimsV2, cmp string, req bool) bool {
|
||||
return verifyIss(claims.GetIssuer(), cmp, req)
|
||||
}
|
||||
|
||||
func NewValidator(opts ...ValidatorOption) *Validator {
|
||||
v := &Validator{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(v)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// ----- helpers
|
||||
|
||||
func verifyAud(aud []string, cmp string, required bool) bool {
|
||||
if len(aud) == 0 {
|
||||
return !required
|
||||
}
|
||||
// use a var here to keep constant time compare when looping over a number of claims
|
||||
result := false
|
||||
|
||||
var stringClaims string
|
||||
for _, a := range aud {
|
||||
if subtle.ConstantTimeCompare([]byte(a), []byte(cmp)) != 0 {
|
||||
result = true
|
||||
}
|
||||
stringClaims = stringClaims + a
|
||||
}
|
||||
|
||||
// case where "" is sent in one or many aud claims
|
||||
if len(stringClaims) == 0 {
|
||||
return !required
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func verifyExp(exp *time.Time, now time.Time, required bool, skew time.Duration) bool {
|
||||
if exp == nil {
|
||||
return !required
|
||||
}
|
||||
|
||||
return now.Before((*exp).Add(+skew))
|
||||
}
|
||||
|
||||
func verifyIat(iat *time.Time, now time.Time, required bool, skew time.Duration) bool {
|
||||
if iat == nil {
|
||||
return !required
|
||||
}
|
||||
|
||||
t := (*iat).Add(-skew)
|
||||
return now.After(t) || now.Equal(t)
|
||||
}
|
||||
|
||||
func verifyNbf(nbf *time.Time, now time.Time, required bool, skew time.Duration) bool {
|
||||
if nbf == nil {
|
||||
return !required
|
||||
}
|
||||
|
||||
t := (*nbf).Add(-skew)
|
||||
return now.After(t) || now.Equal(t)
|
||||
}
|
||||
|
||||
func verifyIss(iss string, cmp string, required bool) bool {
|
||||
if iss == "" {
|
||||
return !required
|
||||
}
|
||||
if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package jwt
|
||||
|
||||
import "time"
|
||||
|
||||
// ValidatorOption is used to implement functional-style options that modify the
|
||||
// behavior of the validator. To add new options, just create a function
|
||||
// (ideally beginning with With or Without) that returns an anonymous function
|
||||
// that takes a *Parser type as input and manipulates its configuration
|
||||
// accordingly.
|
||||
type ValidatorOption func(*Validator)
|
||||
|
||||
// WithLeeway returns the ParserOption for specifying the leeway window.
|
||||
func WithLeeway(leeway time.Duration) ValidatorOption {
|
||||
return func(v *Validator) {
|
||||
v.leeway = leeway
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue