6

我正在使用 dgrijalva/jwt-go 和 lestrrat-go/jwx。我试图实现的是使用 jwks 验证 wso2 jwt。

令牌(过期令牌):

const tokenStr = `eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImI2TnozUDJwMHg1QWpfWENsUmhrVDFzNlNIQSJ9.eyJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9hcHBsaWNhdGlvbnRpZXIiOiJVbmxpbWl0ZWQiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9rZXl0eXBlIjoiUFJPRFVDVElPTiIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL3ZlcnNpb24iOiIxLjAiLCJpc3MiOiJ3c28yLm9yZ1wvcHJvZHVjdHNcL2FtIiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvYXBwbGljYXRpb25uYW1lIjoiVGFseW9uIiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvZW5kdXNlciI6IkZEQkBjYXJib24uc3VwZXIiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9lbmR1c2VyVGVuYW50SWQiOiItMTIzNCIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL3N1YnNjcmliZXIiOiJGREIiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC90aWVyIjoiR29sZCIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL2FwcGxpY2F0aW9uaWQiOiIxNDU2IiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvdXNlcnR5cGUiOiJBUFBMSUNBVElPTiIsImV4cCI6MTU4OTQ2NjI0MSwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvYXBpY29udGV4dCI6IlwvY3VycmVudC1hY2NvdW50XC9jaGVxdWVzXC9hdXRvbWF0aWMtZGVwb3NpdHNcL2F0bVwvMS4wIn0=.K1iPtdXiuicuDPaLC6Exw/7UpJVW6Uy1tPpJlfZ29Vqs9M1zR00JpKxvymQMAzbD0GHlXPPsZmhDxOn0WMAPfr1Xi8tiruTLXNbwUPJ/SOovt+zK4JGtrydhc4iv2EROhMUk2uwJUb4DFjqKZRhBvtCW7fRtdtI9yJL4W4OK8Ld90yOb97usPjEPz8S4E4uNrb5lE2rLzIp+EaPwA232lDkhS8gGPIKdlLG1IdEfQ4cFU1VIplvWoHzprF9mGR0ahT2QGgmGE3AcBfkURk8VzIKDG/UcBA9eHu3XGg28j3OvIXWwJhd7Hi+jTqvggi0hplao8ElvjNBw/wNy2UO9WA==`

jwks:

{"keys":[{"kty":"RSA","e":"AQAB","use":"sig","kid":"MjhhMDk2N2M2NGEwMzgzYjk2OTI3YzdmMGVhOGYxNjI2OTc5Y2Y2MQ","alg":"RS256","n":"zZU9xSgK77PbtkjJgD2Vmmv6_QNe8B54eyOV0k5K2UwuSnhv9RyRA3aL7gDN-qkANemHw3H_4Tc5SKIMltVIYdWlOMW_2m3gDBOODjc1bE-WXEWX6nQkLAOkoFrGW3bgW8TFxfuwgZVTlb6cYkSyiwc5ueFV2xNqo96Qf7nm5E7KZ2QDTkSlNMdW-jIVHMKjuEsy_gtYMaEYrwk5N7VoiYwePaF3I0_g4G2tIrKTLb8DvHApsN1h-s7jMCQFBrY4vCf3RBlYULr4Nz7u8G2NL_L9vURSCU2V2A8rYRkoZoZwk3a3AyJiqeC4T_1rmb8XdrgeFHB5bzXZ7EI0TObhlw"}]}

我看到的大多数例子都使用'kid'并且不相关,因为我的令牌头没有它,它有'x5t'字段..

我还必须注意一件事,我的签名似乎是 base64 编码而不是 base64 url​​ 编码(它几乎混淆了 Parse 方法的使用)。我尝试使用 jwt.Parse() 我尝试手动加密标头和有效负载 sha256,而不是 RS256 和 base64,但没有一个显示成功。

我尝试过的事情:

const tokenString = `..`
func main() {
    t, err := jwt.Parse(tokenStr,  func(t *jwt.Token) (interface{}, error) {
        return []byte("b6Nz3P2p0x5Aj_XClRhkT1s6SHA"), nil
    })
}
4

2 回答 2

2

原始问题的主要问题jwt.KeyFunc是期望一个代表公共加密密钥的 Go 类型,而不是 JWT 的kid.

我有一个用例涉及使用通过 HTTPS 托管的远程 JWKS,但我已经在使用问题作者正在使用的这个 JWT 包github.com/dgrijalva/jwt-go,并且不想切换。

我阅读了一些 RFC 并实现了这个包以jwt.KeyFunc在使用 JWKS 时实现一个。以下是这些软件包如何协同工作的两个示例。

第一个示例展示了如何使用作者的 JWKS 和 JWT 验证令牌。第二个示例显示如果 JWKS 托管在 HTTP 端点上,如何验证作者的 JWT https://keycloak.example.com/auth/realms/example/protocol/openid-connect/certs。请注意原始问题中的 JWT 未正确编码。

来自 JSON 的 JWKS

package main

import (
    "encoding/json"
    "log"

    "github.com/dgrijalva/jwt-go"

    "github.com/MicahParks/keyfunc"
)

func main() {

    // Get the JWKS as JSON.
    var jwksJSON json.RawMessage = []byte(`{"keys":[{"kty":"RSA","e":"AQAB","use":"sig","kid":"MjhhMDk2N2M2NGEwMzgzYjk2OTI3YzdmMGVhOGYxNjI2OTc5Y2Y2MQ","alg":"RS256","n":"zZU9xSgK77PbtkjJgD2Vmmv6_QNe8B54eyOV0k5K2UwuSnhv9RyRA3aL7gDN-qkANemHw3H_4Tc5SKIMltVIYdWlOMW_2m3gDBOODjc1bE-WXEWX6nQkLAOkoFrGW3bgW8TFxfuwgZVTlb6cYkSyiwc5ueFV2xNqo96Qf7nm5E7KZ2QDTkSlNMdW-jIVHMKjuEsy_gtYMaEYrwk5N7VoiYwePaF3I0_g4G2tIrKTLb8DvHApsN1h-s7jMCQFBrY4vCf3RBlYULr4Nz7u8G2NL_L9vURSCU2V2A8rYRkoZoZwk3a3AyJiqeC4T_1rmb8XdrgeFHB5bzXZ7EI0TObhlw"}]}`)

    // Create the JWKS from the resource at the given URL.
    jwks, err := keyfunc.New(jwksJSON)
    if err != nil {
        log.Fatalf("Failed to create JWKS from resource at the given URL.\nError: %s", err.Error())
    }

    // Get a JWT to parse.
    jwtB64 := "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImI2TnozUDJwMHg1QWpfWENsUmhrVDFzNlNIQSJ9.eyJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9hcHBsaWNhdGlvbnRpZXIiOiJVbmxpbWl0ZWQiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9rZXl0eXBlIjoiUFJPRFVDVElPTiIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL3ZlcnNpb24iOiIxLjAiLCJpc3MiOiJ3c28yLm9yZ1wvcHJvZHVjdHNcL2FtIiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvYXBwbGljYXRpb25uYW1lIjoiVGFseW9uIiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvZW5kdXNlciI6IkZEQkBjYXJib24uc3VwZXIiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9lbmR1c2VyVGVuYW50SWQiOiItMTIzNCIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL3N1YnNjcmliZXIiOiJGREIiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC90aWVyIjoiR29sZCIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL2FwcGxpY2F0aW9uaWQiOiIxNDU2IiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvdXNlcnR5cGUiOiJBUFBMSUNBVElPTiIsImV4cCI6MTU4OTQ2NjI0MSwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvYXBpY29udGV4dCI6IlwvY3VycmVudC1hY2NvdW50XC9jaGVxdWVzXC9hdXRvbWF0aWMtZGVwb3NpdHNcL2F0bVwvMS4wIn0=.K1iPtdXiuicuDPaLC6Exw/7UpJVW6Uy1tPpJlfZ29Vqs9M1zR00JpKxvymQMAzbD0GHlXPPsZmhDxOn0WMAPfr1Xi8tiruTLXNbwUPJ/SOovt+zK4JGtrydhc4iv2EROhMUk2uwJUb4DFjqKZRhBvtCW7fRtdtI9yJL4W4OK8Ld90yOb97usPjEPz8S4E4uNrb5lE2rLzIp+EaPwA232lDkhS8gGPIKdlLG1IdEfQ4cFU1VIplvWoHzprF9mGR0ahT2QGgmGE3AcBfkURk8VzIKDG/UcBA9eHu3XGg28j3OvIXWwJhd7Hi+jTqvggi0hplao8ElvjNBw/wNy2UO9WA=="

    // Parse the JWT.
    token, err := jwt.Parse(jwtB64, jwks.KeyFunc)
    if err != nil {
        log.Fatalf("Failed to parse the JWT.\nError: %s", err.Error())
    }

    // Check if the token is valid.
    if !token.Valid {
        log.Fatalf("The token is not valid.")
    }

    log.Println("The token is valid.")
}

来自 HTTPS 端点的 JWKS

package main

import (
    "log"
    "time"

    "github.com/dgrijalva/jwt-go"

    "github.com/MicahParks/keyfunc"
)

func main() {

    // Get the JWKS URL.
    jwksURL := "https://keycloak.example.com/auth/realms/example/protocol/openid-connect/certs"

    // Create the keyfunc options. Refresh the JWKS every hour and log errors.
    refreshInterval := time.Hour
    options := keyfunc.Options{
        RefreshInterval: &refreshInterval,
        RefreshErrorHandler: func(err error) {
            log.Printf("There was an error with the jwt.KeyFunc\nError: %s", err.Error())
        },
    }

    // Create the JWKS from the resource at the given URL.
    jwks, err := keyfunc.Get(jwksURL, options)
    if err != nil {
        log.Fatalf("Failed to create JWKS from resource at the given URL.\nError: %s", err.Error())
    }

    // Get a JWT to parse.
    jwtB64 := "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImI2TnozUDJwMHg1QWpfWENsUmhrVDFzNlNIQSJ9.eyJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9hcHBsaWNhdGlvbnRpZXIiOiJVbmxpbWl0ZWQiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9rZXl0eXBlIjoiUFJPRFVDVElPTiIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL3ZlcnNpb24iOiIxLjAiLCJpc3MiOiJ3c28yLm9yZ1wvcHJvZHVjdHNcL2FtIiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvYXBwbGljYXRpb25uYW1lIjoiVGFseW9uIiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvZW5kdXNlciI6IkZEQkBjYXJib24uc3VwZXIiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC9lbmR1c2VyVGVuYW50SWQiOiItMTIzNCIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL3N1YnNjcmliZXIiOiJGREIiLCJodHRwOlwvXC93c28yLm9yZ1wvY2xhaW1zXC90aWVyIjoiR29sZCIsImh0dHA6XC9cL3dzbzIub3JnXC9jbGFpbXNcL2FwcGxpY2F0aW9uaWQiOiIxNDU2IiwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvdXNlcnR5cGUiOiJBUFBMSUNBVElPTiIsImV4cCI6MTU4OTQ2NjI0MSwiaHR0cDpcL1wvd3NvMi5vcmdcL2NsYWltc1wvYXBpY29udGV4dCI6IlwvY3VycmVudC1hY2NvdW50XC9jaGVxdWVzXC9hdXRvbWF0aWMtZGVwb3NpdHNcL2F0bVwvMS4wIn0=.K1iPtdXiuicuDPaLC6Exw/7UpJVW6Uy1tPpJlfZ29Vqs9M1zR00JpKxvymQMAzbD0GHlXPPsZmhDxOn0WMAPfr1Xi8tiruTLXNbwUPJ/SOovt+zK4JGtrydhc4iv2EROhMUk2uwJUb4DFjqKZRhBvtCW7fRtdtI9yJL4W4OK8Ld90yOb97usPjEPz8S4E4uNrb5lE2rLzIp+EaPwA232lDkhS8gGPIKdlLG1IdEfQ4cFU1VIplvWoHzprF9mGR0ahT2QGgmGE3AcBfkURk8VzIKDG/UcBA9eHu3XGg28j3OvIXWwJhd7Hi+jTqvggi0hplao8ElvjNBw/wNy2UO9WA=="

    // Parse the JWT.
    token, err := jwt.Parse(jwtB64, jwks.KeyFunc)
    if err != nil {
        log.Fatalf("Failed to parse the JWT.\nError: %s", err.Error())
    }

    // Check if the token is valid.
    if !token.Valid {
        log.Fatalf("The token is not valid.")
    }

    log.Println("The token is valid.")
}
于 2021-03-11T01:00:46.460 回答
0

您可能已经注意到,github.com/lestrrat-go/jwx仅支持 RawURLEncode base64 格式。最好的解决方案是您可以使用该特定格式的令牌。

作为一个 hacky 解决方法,您可以手动将令牌重新编码为 RawURLEncode 格式并将该令牌提供给jwt.ParseString()函数:

// token consists of three parts (header, payload, signature) separeted by '.'
stdEncodedParts := strings.Split(tokenStr, ".")
var rawURLEncodedParts []string
for _, part := range stdEncodedParts {
    rawpart, err := base64.StdEncoding.DecodeString(part)
    if err != nil {
        panic(err)
    }
    rawURLEncodedParts = append(rawURLEncodedParts, base64.RawURLEncoding.EncodeToString(rawpart))
}

rawURLEncodedToken := strings.Join(rawURLEncodedParts, ".")
token, err := jwt.ParseString(rawURLEncodedToken)

至于验证部分,它看起来像下面的代码,虽然我没有设法验证你的令牌。您确定您提供了正确的验证密钥吗?另请注意,您可能必须向 jws.VerifyWithJWKSet() 提供定制的令牌,因为

  1. 它无法解析原始 StdEncoded 格式的签名

  2. 但同时,应该在原始 StdEncoded 字符串的前半部分进行签名,因此您无法按原样提供重新编码的字符串。

const jwksStr = `{"keys":[{"kty":"RSA","e":"AQAB","use":"sig","kid":"MjhhMDk2N2M2NGEwMzgzYjk2OTI3YzdmMGVhOGYxNjI2OTc5Y2Y2MQ","alg":"RS256","n":"zZU9xSgK77PbtkjJgD2Vmmv6_QNe8B54eyOV0k5K2UwuSnhv9RyRA3aL7gDN-qkANemHw3H_4Tc5SKIMltVIYdWlOMW_2m3gDBOODjc1bE-WXEWX6nQkLAOkoFrGW3bgW8TFxfuwgZVTlb6cYkSyiwc5ueFV2xNqo96Qf7nm5E7KZ2QDTkSlNMdW-jIVHMKjuEsy_gtYMaEYrwk5N7VoiYwePaF3I0_g4G2tIrKTLb8DvHApsN1h-s7jMCQFBrY4vCf3RBlYULr4Nz7u8G2NL_L9vURSCU2V2A8rYRkoZoZwk3a3AyJiqeC4T_1rmb8XdrgeFHB5bzXZ7EI0TObhlw"}]}`
customTokenStr := stdEncodedParts[0] + "." + stdEncodedParts[1] + "." + rawURLEncodedParts[2]
jwks, err := jwk.ParseString(jwksStr)
if err != nil {
    panic(err)
}
_, err = jws.VerifyWithJWKSet([]byte(customTokenStr), jwks, nil)
于 2020-05-17T13:56:50.847 回答