简体   繁体   中英

How to verify a JWT token in Go and load it into a struct?

This should be a simple question but I've been frustrated trying to figure it out.

Given a JWT token and a RSA public/private key pair, how do I convert the token to a struct and verify it?

type JwtData struct {
    AccessToken string `json:"access_token"`
    Iat         int    `json:"iat"`
    Exp         int    `json:"exp"`
    Sub         string `json:"sub"`
    Iss         string `json:"iss"`
}

privateKeyString := "my_private_key"

publicKeyString := "my_public_key"

tokenString := "my_token_data"

decryptedData, error := whatDoIDoHere(publicKeyString, tokenString)

It seems like this is the "standard" Go library for JWTs (is there even a standard one? I find Go's package ecosystem confusing) https://github.com/lestrrat-go/jwx/tree/main/jwt

The demo covers this fairly well but here is a more complete example showing how to go from an encoded token/certificate (note that the demo generates the cert/jwt first; you can ignore that bit if you are not interested in it).

It seems like this is the "standard" Go library for JWTs (is there even a standard one?)

Go has a Standard Library which covers a lot of basic functionality. I don't think that anything else can be considered 'standard' (this is as per other languages; the Go standard library is pretty comprehensive compared to most). There are a few JWT packages but lestrrat-go/jwx is fairly comprehensive and, importantly, actively developed.

package main

import (
    "bytes"
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "time"

    "github.com/lestrrat-go/jwx/jwa"
    "github.com/lestrrat-go/jwx/jwk"
    "github.com/lestrrat-go/jwx/jwt"
)

func main() {
    token, pubKey, err := generateJWT()
    if err != nil {
        panic(err)
    }
    // JWT has been generated - lets output it (and the certificate)
    fmt.Printf("JWT: %s\nPublic Key: %s", token, pubKey)

    parsedToken, err := parseJWT(token, pubKey)
    if err != nil {
        panic(err)
    }
    // Standard claims can be accessed as follows:
    fmt.Printf("Iss: %v\n", parsedToken.IssuedAt())
    fmt.Printf("Exp: %v\n", parsedToken.Expiration())
    fmt.Printf("Aud: %v\n", parsedToken.Audience())

    // Private Claims
    fmt.Printf("Private Claims: %#v\n", parsedToken.PrivateClaims())
}

// generateJWT - Generates a JWT (and signs it with a generated certificate)
func generateJWT() ([]byte, []byte, error) {
    // We will start by generating a test token for which a key will be needed
    privKey, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        return nil, nil, fmt.Errorf("failed to generate private key: %s\n", err)
    }

    // Preparation:
    // For demonstration purposes, we need to do some preparation
    // Create a JWK key to sign the token (and also give a KeyID)
    realKey, err := jwk.New(privKey)
    if err != nil {
        return nil, nil, fmt.Errorf("failed to create JWK: %s\n", err)
    }
    realKey.Set(jwk.KeyIDKey, `mykey`)

    // Create the token
    token := jwt.New()
    token.Set(`foo`, `bar`)

    // Set a few standard keys
    token.Set(jwt.IssuedAtKey, time.Now())
    token.Set(jwt.ExpirationKey,  time.Now().Add(time.Hour)) // lets make it expire in an hour
    token.Set(jwt.AudienceKey, "lotsOfUsers")

    // Sign the token and generate a payload
    signed, err := jwt.Sign(token, jwa.RS256, realKey)
    if err != nil {
        return nil, nil, fmt.Errorf("failed to generate signed payload: %s\n", err)
    }

    // For completeness lets encode the public key (so the decoding matches what someone just performing the decode would need to do)
    publickey := &privKey.PublicKey
    publicKeyBytes, err := x509.MarshalPKIXPublicKey(publickey)
    if err != nil {
        return nil, nil, fmt.Errorf("error when dumping publickey: %s \n", err)
    }
    publicKeyBlock := &pem.Block{
        Type:  "PUBLIC KEY",
        Bytes: publicKeyBytes,
    }
    publicKeyBuff := new(bytes.Buffer)
    err = pem.Encode(publicKeyBuff, publicKeyBlock)
    if err != nil {
        return nil, nil, fmt.Errorf("error when encode public pem: %s \n", err)
    }
    encodedPubKey := publicKeyBuff.Bytes()

    return signed, encodedPubKey, nil
}

// parse the passed in JWT with the passed in certificate and return a map of claims
func parseJWT(signedJWT []byte, encodedPubKey []byte) (jwt.Token, error){
    var keyset jwk.Set
    privPem, _ := pem.Decode(encodedPubKey)
    if privPem == nil {
        return nil, fmt.Errorf("Failed to decode PEM block")
    }
    privPemBytes := privPem.Bytes
    var parsedKeyInt interface{}
    var err error
    if parsedKeyInt, err = x509.ParsePKIXPublicKey(privPemBytes); err != nil {
        return nil, fmt.Errorf("Failed to decode cert: %s", err)
    }
    parsedKey, ok := parsedKeyInt.(*rsa.PublicKey)
    if !ok {
        return nil, fmt.Errorf("public key not expected type")
    }

    // Now create a key set that users will use to verity the signed payload against
    pubKey, err := jwk.New(parsedKey)
    if err != nil {
        return nil, fmt.Errorf("failed to create JWK: %s", err)
    }

    // Remember, the key must have the proper "kid"
    pubKey.Set(jwk.KeyIDKey, "mykey")

    // This key set contains only one key (the correct one)
    keyset = jwk.NewSet()
    keyset.Add(pubKey)

    parsedToken, err := jwt.Parse(
        signedJWT,
        // Tell the parser that you want to use this keyset
        jwt.WithKeySet(keyset),
        // Tell the parser that you can trust this KeySet, and that
        // yo want to use the sole key in it
        jwt.UseDefaultKey(true),
        // We want to validate the token; e.g. it should not have expired
        jwt.WithValidate(true),
    )
    if err != nil {
        return nil, fmt.Errorf("failed to parse payload: %s", err)
    }

    return parsedToken, nil
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM