How to verify JWT signature with JWK in Go?

huangapple go评论73阅读模式
英文:

How to verify JWT signature with JWK in Go?

问题

我一直在寻找一个我能理解的使用Go语言验证JWT签名的示例。

这可能会有些棘手,因为我正在使用Okta,它使用JWKs,所以并不是特别直接。

当我收到一个JWT时,我可以轻松解码它。我只是卡在如何验证签名上。

下面我包含了一个JWT和JWK的详细信息。有人可以提供一个带有示例的签名验证吗?

你可以在https://oktaproxy.com/oidcgenerator.php上获取所有这些信息-这个网站将从Okta生成一个JWT,密钥可以在https://companyx.okta.com/oauth2/v1/keys获取。

这是JWT:

eyJhbGciOiJSUzI1NiIsImtpZCI6Ind5TXdLNEE2Q0w5UXcxMXVvZlZleVExMTlYeVgteHlreW1ra1h5Z1o1T00ifQ.eyJzdWIiOiIwMHUxOGVlaHUzNDlhUzJ5WDFkOCIsIm5hbWUiOiJva3RhcHJveHkgb2t0YXByb3h5IiwidmVyIjoxLCJpc3MiOiJodHRwczovL2NvbXBhbnl4Lm9rdGEuY29tIiwiYXVkIjoidlpWNkNwOHJuNWx4ck45YVo2ODgiLCJpYXQiOjE0ODEzODg0NTMsImV4cCI6MTQ4MTM5MjA1MywianRpIjoiSUQuWm9QdVdIR3IxNkR6a3RUbEdXMFI4b1lRaUhnVWg0aUotTHo3Z3BGcGItUSIsImFtciI6WyJwd2QiXSwiaWRwIjoiMDBveTc0YzBnd0hOWE1SSkJGUkkiLCJub25jZSI6Im4tMFM2X1d6QTJNaiIsInByZWZlcnJlZF91c2VybmFtZSI6Im9rdGFwcm94eUBva3RhLmNvbSIsImF1dGhfdGltZSI6MTQ4MTM4ODQ0MywiYXRfaGFzaCI6Im1YWlQtZjJJczhOQklIcV9CeE1ISFEifQ.OtVyCK0sE6Cuclg9VMD2AwLhqEyq2nv3a1bfxlzeS-bdu9KtYxcPSxJ6vxMcSSbMIIq9eEz9JFMU80zqgDPHBCjlOsC5SIPz7mm1Z3gCwq4zsFJ-2NIzYxA3p161ZRsPv_3bUyg9B_DPFyBoihgwWm6yrvrb4rmHXrDkjxpxCLPp3OeIpc_kb2t8r5HEQ5UBZPrsiScvuoVW13YwWpze59qBl_84n9xdmQ5pS7DklzkAVgqJT_NWBlb5uo6eW26HtJwHzss7xOIdQtcOtC1Gj3O82a55VJSQnsEEBeqG1ESb5Haq_hJgxYQnBssKydPCIxdZiye-0Ll9L8wWwpzwig

这是密钥:

{
   "keys":[
      {
         "alg":"RS256",
         "e":"AQAB",
         "n":"ok6rvXu95337IxsDXrKzlIqw_I_zPDG8JyEw2CTOtNMoDi1QzpXQVMGj2snNEmvNYaCTmFf51I-EDgeFLLexr40jzBXlg72quV4aw4yiNuxkigW0gMA92OmaT2jMRIdDZM8mVokoxyPfLub2YnXHFq0XuUUgkX_TlutVhgGbyPN0M12teYZtMYo2AUzIRggONhHvnibHP0CPWDjCwSfp3On1Recn4DPxbn3DuGslF2myalmCtkujNcrhHLhwYPP-yZFb8e0XSNTcQvXaQxAqmnWH6NXcOtaeWMQe43PNTAyNinhndgI8ozG3Hz-1NzHssDH_yk6UYFSszhDbWAzyqw",
         "kid":"wyMwK4A6CL9Qw11uofVeyQ119XyX-xykymkkXygZ5OM",
         "kty":"RSA",
         "use":"sig"
      },
      {
         "alg":"RS256",
         "e":"AQAB",
         "n":"nXv6FSAcMjuanQ2hIIUb8Vkqe94t98kPh2T8-0j6-Jq8pOclgKdtVeIZcBE9F_XiuJvg4b6WVs-uvA-pS8mmMvQ21xU5Q_37Cojv8v_QlHWETHwEJdXXiY2Xq5LgXDSwEhhdDZHSMQYDuvhp_P6nl2LNqqUvJkjoFWcnn2btgSIUQROIaDdxtx7_2h4oUi5u11BGSF2SZZiEpDAKT08Htv3uwXdwDA6ll99fbi8X8RmH5oY_tIZTeIzu50qHxElPewoYO8QrJYsO9oFcCPMHGxYWjXQEa-QZYgo0wS9zRIkeJc5kshc4-9Uhv2DVIjk_-ofGlML9ieggGyillBKptw",
         "kid":"GRF55Lbzgg4sANCmER-sm55eX_qUOpY8UTptDmDG_6U",
         "kty":"RSA",
         "use":"sig"
      }
   ]
}
英文:

I have been searching for an example I can understand of how to validate the signature of a JWT with the Go Language.

This might be especially tricky since I am using Okta, and it uses JWKs, so it is not especially straight forward.

When I receive a JWT, I can decode it no problem. I just get stuck on how to verify the signature.

Below I have included a JWT and the JWK details. Can anyone provide signature validation with an example?

You can get to all of this information at https://oktaproxy.com/oidcgenerator.php — this site will generate a JWT from Okta, and the keys can be obtained at https://companyx.okta.com/oauth2/v1/keys.

Here is the JWT:

eyJhbGciOiJSUzI1NiIsImtpZCI6Ind5TXdLNEE2Q0w5UXcxMXVvZlZleVExMTlYeVgteHlreW1ra1h5Z1o1T00ifQ.eyJzdWIiOiIwMHUxOGVlaHUzNDlhUzJ5WDFkOCIsIm5hbWUiOiJva3RhcHJveHkgb2t0YXByb3h5IiwidmVyIjoxLCJpc3MiOiJodHRwczovL2NvbXBhbnl4Lm9rdGEuY29tIiwiYXVkIjoidlpWNkNwOHJuNWx4ck45YVo2ODgiLCJpYXQiOjE0ODEzODg0NTMsImV4cCI6MTQ4MTM5MjA1MywianRpIjoiSUQuWm9QdVdIR3IxNkR6a3RUbEdXMFI4b1lRaUhnVWg0aUotTHo3Z3BGcGItUSIsImFtciI6WyJwd2QiXSwiaWRwIjoiMDBveTc0YzBnd0hOWE1SSkJGUkkiLCJub25jZSI6Im4tMFM2X1d6QTJNaiIsInByZWZlcnJlZF91c2VybmFtZSI6Im9rdGFwcm94eUBva3RhLmNvbSIsImF1dGhfdGltZSI6MTQ4MTM4ODQ0MywiYXRfaGFzaCI6Im1YWlQtZjJJczhOQklIcV9CeE1ISFEifQ.OtVyCK0sE6Cuclg9VMD2AwLhqEyq2nv3a1bfxlzeS-bdu9KtYxcPSxJ6vxMcSSbMIIq9eEz9JFMU80zqgDPHBCjlOsC5SIPz7mm1Z3gCwq4zsFJ-2NIzYxA3p161ZRsPv_3bUyg9B_DPFyBoihgwWm6yrvrb4rmHXrDkjxpxCLPp3OeIpc_kb2t8r5HEQ5UBZPrsiScvuoVW13YwWpze59qBl_84n9xdmQ5pS7DklzkAVgqJT_NWBlb5uo6eW26HtJwHzss7xOIdQtcOtC1Gj3O82a55VJSQnsEEBeqG1ESb5Haq_hJgxYQnBssKydPCIxdZiye-0Ll9L8wWwpzwig

Here are the Keys:

{
   "keys":[
      {
         "alg":"RS256",
         "e":"AQAB",
         "n":"ok6rvXu95337IxsDXrKzlIqw_I_zPDG8JyEw2CTOtNMoDi1QzpXQVMGj2snNEmvNYaCTmFf51I-EDgeFLLexr40jzBXlg72quV4aw4yiNuxkigW0gMA92OmaT2jMRIdDZM8mVokoxyPfLub2YnXHFq0XuUUgkX_TlutVhgGbyPN0M12teYZtMYo2AUzIRggONhHvnibHP0CPWDjCwSfp3On1Recn4DPxbn3DuGslF2myalmCtkujNcrhHLhwYPP-yZFb8e0XSNTcQvXaQxAqmnWH6NXcOtaeWMQe43PNTAyNinhndgI8ozG3Hz-1NzHssDH_yk6UYFSszhDbWAzyqw",
         "kid":"wyMwK4A6CL9Qw11uofVeyQ119XyX-xykymkkXygZ5OM",
         "kty":"RSA",
         "use":"sig"
      },
      {
         "alg":"RS256",
         "e":"AQAB",
         "n":"nXv6FSAcMjuanQ2hIIUb8Vkqe94t98kPh2T8-0j6-Jq8pOclgKdtVeIZcBE9F_XiuJvg4b6WVs-uvA-pS8mmMvQ21xU5Q_37Cojv8v_QlHWETHwEJdXXiY2Xq5LgXDSwEhhdDZHSMQYDuvhp_P6nl2LNqqUvJkjoFWcnn2btgSIUQROIaDdxtx7_2h4oUi5u11BGSF2SZZiEpDAKT08Htv3uwXdwDA6ll99fbi8X8RmH5oY_tIZTeIzu50qHxElPewoYO8QrJYsO9oFcCPMHGxYWjXQEa-QZYgo0wS9zRIkeJc5kshc4-9Uhv2DVIjk_-ofGlML9ieggGyillBKptw",
         "kid":"GRF55Lbzgg4sANCmER-sm55eX_qUOpY8UTptDmDG_6U",
         "kty":"RSA",
         "use":"sig"
      }
   ]
}

答案1

得分: 48

以下是JWT解码和验证的示例。它使用了jwt-gojwk包:

package main

import (
	"errors"
	"fmt"

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

const token = `eyJhbGciOiJSUzI1NiIsImtpZCI6Ind5TXdLNEE2Q0w5UXcxMXVvZlZleVExMTlYeVgteHlreW1ra1h5Z1o1T00ifQ.eyJzdWIiOiIwMHUxOGVlaHUzNDlhUzJ5WDFkOCIsIm5hbWUiOiJva3RhcHJveHkgb2t0YXByb3h5IiwidmVyIjoxLCJpc3MiOiJodHRwczovL2NvbXBhbnl4Lm9rdGEuY29tIiwiYXVkIjoidlpWNkNwOHJuNWx4ck45YVo2ODgiLCJpYXQiOjE0ODEzODg0NTMsImV4cCI6MTQ4MTM5MjA1MywianRpIjoiSUQuWm9QdVdIR3IxNkR6a3RUbEdXMFI4b1lRaUhnVWg0aUotTHo3Z3BGcGItUSIsImFtciI6WyJwd2QiXSwiaWRwIjoiMDBveTc0YzBnd0hOWE1SSkJGUkkiLCJub25jZSI6Im4tMFM2X1d6QTJNaiIsInByZWZlcnJlZF91c2VybmFtZSI6Im9rdGFwcm94eUBva3RhLmNvbSIsImF1dGhfdGltZSI6MTQ4MTM4ODQ0MywiYXRfaGFzaCI6Im1YWlQtZjJJczhOQklIcV9CeE1ISFEifQ.OtVyCK0sE6Cuclg9VMD2AwLhqEyq2nv3a1bfxlzeS-bdu9KtYxcPSxJ6vxMcSSbMIIq9eEz9JFMU80zqgDPHBCjlOsC5SIPz7mm1Z3gCwq4zsFJ-2NIzYxA3p161ZRsPv_3bUyg9B_DPFyBoihgwWm6yrvrb4rmHXrDkjxpxCLPp3OeIpc_kb2t8r5HEQ5UBZPrsiScvuoVW13YwWpze59qBl_84n9xdmQ5pS7DklzkAVgqJT_NWBlb5uo6eW26HtJwHzss7xOIdQtcOtC1Gj3O82a55VJSQnsEEBeqG1ESb5Haq_hJgxYQnBssKydPCIxdZiye-0Ll9L8wWwpzwig`

const jwksURL = `https://companyx.okta.com/oauth2/v1/keys`

func getKey(token *jwt.Token) (interface{}, error) {

	// TODO: cache response so we don't have to make a request every time
	// we want to verify a JWT
	set, err := jwk.FetchHTTP(jwksURL)
	if err != nil {
		return nil, err
	}

	keyID, ok := token.Header["kid"].(string)
	if !ok {
		return nil, errors.New("expecting JWT header to have string kid")
	}

	if key := set.LookupKeyID(keyID); len(key) == 1 {
		return key[0].Materialize()
	}

	return nil, fmt.Errorf("unable to find key %q", keyID)
}

func main() {
	token, err := jwt.Parse(token, getKey)
	if err != nil {
		panic(err)
	}
	claims := token.Claims.(jwt.MapClaims)
	for key, value := range claims {
		fmt.Printf("%s\t%v\n", key, value)
	}
}

希望对你有帮助!如果有任何其他问题,请随时问我。

<details>
<summary>英文:</summary>

Below is an example of JWT decoding and verification. It uses both the [jwt-go][1] and [jwk][2] packages:

    package main
    
    import (
    	&quot;errors&quot;
    	&quot;fmt&quot;
    
    	&quot;github.com/dgrijalva/jwt-go&quot;
    	&quot;github.com/lestrrat-go/jwx/jwk&quot;
    )
    
    const token = `eyJhbGciOiJSUzI1NiIsImtpZCI6Ind5TXdLNEE2Q0w5UXcxMXVvZlZleVExMTlYeVgteHlreW1ra1h5Z1o1T00ifQ.eyJzdWIiOiIwMHUxOGVlaHUzNDlhUzJ5WDFkOCIsIm5hbWUiOiJva3RhcHJveHkgb2t0YXByb3h5IiwidmVyIjoxLCJpc3MiOiJodHRwczovL2NvbXBhbnl4Lm9rdGEuY29tIiwiYXVkIjoidlpWNkNwOHJuNWx4ck45YVo2ODgiLCJpYXQiOjE0ODEzODg0NTMsImV4cCI6MTQ4MTM5MjA1MywianRpIjoiSUQuWm9QdVdIR3IxNkR6a3RUbEdXMFI4b1lRaUhnVWg0aUotTHo3Z3BGcGItUSIsImFtciI6WyJwd2QiXSwiaWRwIjoiMDBveTc0YzBnd0hOWE1SSkJGUkkiLCJub25jZSI6Im4tMFM2X1d6QTJNaiIsInByZWZlcnJlZF91c2VybmFtZSI6Im9rdGFwcm94eUBva3RhLmNvbSIsImF1dGhfdGltZSI6MTQ4MTM4ODQ0MywiYXRfaGFzaCI6Im1YWlQtZjJJczhOQklIcV9CeE1ISFEifQ.OtVyCK0sE6Cuclg9VMD2AwLhqEyq2nv3a1bfxlzeS-bdu9KtYxcPSxJ6vxMcSSbMIIq9eEz9JFMU80zqgDPHBCjlOsC5SIPz7mm1Z3gCwq4zsFJ-2NIzYxA3p161ZRsPv_3bUyg9B_DPFyBoihgwWm6yrvrb4rmHXrDkjxpxCLPp3OeIpc_kb2t8r5HEQ5UBZPrsiScvuoVW13YwWpze59qBl_84n9xdmQ5pS7DklzkAVgqJT_NWBlb5uo6eW26HtJwHzss7xOIdQtcOtC1Gj3O82a55VJSQnsEEBeqG1ESb5Haq_hJgxYQnBssKydPCIxdZiye-0Ll9L8wWwpzwig`
    
    const jwksURL = `https://companyx.okta.com/oauth2/v1/keys`
    
    func getKey(token *jwt.Token) (interface{}, error) {
    
    	// TODO: cache response so we don&#39;t have to make a request every time
    	// we want to verify a JWT
    	set, err := jwk.FetchHTTP(jwksURL)
    	if err != nil {
    		return nil, err
    	}
    
    	keyID, ok := token.Header[&quot;kid&quot;].(string)
    	if !ok {
    		return nil, errors.New(&quot;expecting JWT header to have string kid&quot;)
    	}
    
    	if key := set.LookupKeyID(keyID); len(key) == 1 {
    		return key[0].Materialize()
    	}
    
    	return nil, fmt.Errorf(&quot;unable to find key %q&quot;, keyID)
    }
    
    func main() {
    	token, err := jwt.Parse(token, getKey)
    	if err != nil {
    		panic(err)
    	}
    	claims := token.Claims.(jwt.MapClaims)
    	for key, value := range claims {
    		fmt.Printf(&quot;%s\t%v\n&quot;, key, value)
    	}
    }


  [1]: https://godoc.org/github.com/dgrijalva/jwt-go
  [2]: https://godoc.org/github.com/lestrrat-go/jwx/jwk

</details>



# 答案2
**得分**: 6

我最近遇到了一个非常相似的用例,所以我阅读了一些RFC并编写了这个包:[`github.com/MicahParks/keyfunc`](https://github.com/MicahParks/keyfunc)。

它允许你使用最流行的JWT包[`github.com/golang-jwt/jwt/v4`](https://github.com/golang-jwt/jwt)(之前是[`github.com/dgrijalva/jwt-go`](https://github.com/dgrijalva/jwt-go))来解析令牌。它还可以在后台goroutine中自动重新加载你的JWKS内容。

使用你的JWKS和JWT,这里有两个示例。第一个示例将通过HTTPS从远程URL加载JWKS。第二个示例将从静态JSON加载JWKS。

# 通过HTTPS从托管的JWKS加载
```go
package main

import (
	"context"
	"log"
	"time"

	"github.com/golang-jwt/jwt/v4"

	"github.com/MicahParks/keyfunc"
)

func main() {
	// 获取JWKS URL。
	//
	// 这是一个示例JWKS服务。访问https://jwks-service.appspot.com/并获取一个令牌以测试此示例。
	jwksURL := "https://jwks-service.appspot.com/.well-known/jwks.json"

	// 创建一个上下文,当取消时,结束JWKS后台刷新的goroutine。
	ctx, cancel := context.WithCancel(context.Background())

	// 创建keyfunc选项。使用一个记录日志的错误处理程序。当发现由未知KID签名的JWT或在指定的间隔时间内刷新JWKS时,刷新JWKS。限制刷新速率。在初始JWKS刷新请求超时后,超时时间为10秒。这个超时时间也用于创建keyfunc.Get的初始context.Context。
	options := keyfunc.Options{
		Ctx: ctx,
		RefreshErrorHandler: func(err error) {
			log.Printf("jwt.Keyfunc发生错误\n错误:%s", err.Error())
		},
		RefreshInterval:   time.Hour,
		RefreshRateLimit:  time.Minute * 5,
		RefreshTimeout:    time.Second * 10,
		RefreshUnknownKID: true,
	}

	// 从给定URL的资源创建JWKS。
	jwks, err := keyfunc.Get(jwksURL, options)
	if err != nil {
		log.Fatalf("无法从给定URL的资源创建JWKS。\n错误:%s", err.Error())
	}

	// 获取要解析的JWT。
	jwtB64 := "eyJraWQiOiJlZThkNjI2ZCIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJXZWlkb25nIiwiYXVkIjoiVGFzaHVhbiIsImlzcyI6Imp3a3Mtc2VydmljZS5hcHBzcG90LmNvbSIsImlhdCI6MTYzMTM2OTk1NSwianRpIjoiNDY2M2E5MTAtZWU2MC00NzcwLTgxNjktY2I3NDdiMDljZjU0In0.LwD65d5h6U_2Xco81EClMa_1WIW4xXZl8o4b7WzY_7OgPD2tNlByxvGDzP7bKYA9Gj--1mi4Q4li4CAnKJkaHRYB17baC0H5P9lKMPuA6AnChTzLafY6yf-YadA7DmakCtIl7FNcFQQL2DXmh6gS9J6TluFoCIXj83MqETbDWpL28o3XAD_05UP8VLQzH2XzyqWKi97mOuvz-GsDp9mhBYQUgN3csNXt2v2l-bUPWe19SftNej0cxddyGu06tXUtaS6K0oe0TTbaqc3hmfEiu5G0J8U6ztTUMwXkBvaknE640NPgMQJqBaey0E4u0txYgyvMvvxfwtcOrDRYqYPBnA"

	// 解析JWT。
	token, err := jwt.Parse(jwtB64, jwks.Keyfunc)
	if err != nil {
		log.Fatalf("无法解析JWT。\n错误:%s", err.Error())
	}

	// 检查令牌是否有效。
	if !token.Valid {
		log.Fatalf("令牌无效。")
	}
	log.Println("令牌有效。")

	// 当不再需要后台刷新goroutine时,结束它。
	cancel()

	// 这将无效,因为上面的代码取消了父context.Context。
	// 这个方法调用是幂等的,类似于context.CancelFunc。
	jwks.EndBackground()
}

从JSON的JWKS

package main

import (
	"encoding/json"
	"log"

	"github.com/golang-jwt/jwt/v4"

	"github.com/MicahParks/keyfunc"
)

func main() {
	// 获取JSON格式的JWKS。
	jwksJSON := json.RawMessage(`{"keys":[{"kty":"RSA","e":"AQAB","kid":"ee8d626d","n":"gRda5b0pkgTytDuLrRnNSYhvfMIyM0ASq2ZggY4dVe12JV8N7lyXilyqLKleD-2lziivvzE8O8CdIC2vUf0tBD7VuMyldnZruSEZWCuKJPdgKgy9yPpShmD2NyhbwQIAbievGMJIp_JMwz8MkdY5pzhPECGNgCEtUAmsrrctP5V8HuxaxGt9bb-DdPXkYWXW3MPMSlVpGZ5GiIeTABxqYNG2MSoYeQ9x8O3y488jbassTqxExI_4w9MBQBJR9HIXjWrrrenCcDlMY71rzkbdj3mmcn9xMq2vB5OhfHyHTihbUPLSm83aFWSuW9lE7ogMc93XnrB8evIAk6VfsYlS9Q"},{"kty":"EC","crv":"P-256","kid":"711d48d1","x":"tfXCoBU-wXemeQCkME1gMZWK0-UECCHIkedASZR0t-Q","y":"9xzYtnKQdiQJHCtGwpZWF21eP1fy5x4wC822rCilmBw"},{"kty":"EC","crv":"P-384","kid":"d52c9829","x":"tFx6ev6eLs9sNfdyndn4OgbhV6gPFVn7Ul0VD5vwuplJLbIYeFLI6T42tTaE5_Q4","y":"A0gzB8TqxPX7xMzyHH_FXkYG2iROANH_kQxBovSeus6l_QSyqYlipWpBy9BhY9dz"},{"kty":"RSA","e":"AQAB","kid":"ecac72e5","n":"nLbnTvZAUxdmuAbDDUNAfha6mw0fri3UpV2w1PxilflBuSnXJhzo532-YQITogoanMjy_sQ8kHUhZYHVRR6vLZRBBbl-hP8XWiCe4wwioy7Ey3TiIUYfW-SD6I42XbLt5o-47IR0j5YDXxnX2UU7-UgR_kITBeLDfk0rSp4B0GUhPbP5IDItS0MHHDDS3lhvJomxgEfoNrp0K0Fz_s0K33hfOqc2hD1tSkX-3oDTQVRMF4Nxax3NNw8-ahw6HNMlXlwWfXodgRMvj9pcz8xUYa3C5IlPlZkMumeNCFx1qds6K_eYcU0ss91DdbhhE8amRX1FsnBJNMRUkA5i45xkOIx15rQN230zzh0p71jvtx7wYRr5pdMlwxV0T9Ck5PCmx-GzFazA2X6DJ0Xnn1-cXkRoZHFj_8Mba1dUrNz-NWEk83uW5KT-ZEbX7nzGXtayKWmGb873a8aYPqIsp6bQ_-eRBd8TDT2g9HuPyPr5VKa1p33xKaohz4DGy3t1Qpy3UWnbPXUlh5dLWPKz-TcS9FP5gFhWVo-ZhU03Pn6P34OxHmXGWyQao18dQGqzgD4e9vY3rLhfcjVZJYNlWY2InsNwbYS-DnienPf1ws-miLeXxNKG3tFydoQzHwyOxG6Wc-HBfzL_hOvxINKQamvPasaYWl1LWznMps6elKCgKDc"},{"kty":"EC","crv":"P-521","kid":"c570888f","x":"AHNpXq0J7rikNRlwhaMYDD8LGVAVJzNJ-jEPksUIn2LB2LCdNRzfAhgbxdQcWT9ktlc9M1EhmTLccEqfnWdGL9G1","y":"AfHPUW3GYzzqbTczcYR0nYMVMFVrYsUxv4uiuSNV_XRN3Jf8zeYbbOLJv4S3bUytO7qHY8bfZxPxR9nn3BBTf5ol"}]}`)

	// 从给定的JSON创建JWKS。
	jwks, err := keyfunc.NewJSON(jwksJSON)
	if err != nil {
		log.Fatalf("无法从JSON创建JWKS。\n错误:%s", err.Error())
	}

	// 获取要解析的JWT。
	jwtB64 := "eyJraWQiOiJlZThkNjI2ZCIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJXZWlkb25nIiwiYXVkIjoiVGFzaHVhbiIsImlzcyI6Imp3a3Mtc2VydmljZS5hcHBzcG90LmNvbSIsImlhdCI6MTYzMTM2OTk1NSwianRpIjoiNDY2M2E5MTAtZWU2MC00NzcwLTgxNjktY2I3NDdiMDljZjU0In0.LwD65d5h6U_2Xco81EClMa_1WIW4xXZl8o4b7WzY_7OgPD2tNlByxvGDzP7bKYA9Gj--1mi4Q4li4CAnKJkaHRYB17baC0H5P9lKMPuA6AnChTzLafY6yf-YadA7DmakCtIl7FNcFQQL2DXmh6gS9J6TluFoCIXj83MqETbDWpL28o3XAD_05UP8VLQzH2XzyqWKi97mOuvz-GsDp9mhBYQUgN3csNXt2v2l-bUPWe19SftNej0cxddyGu06tXUtaS6K0oe0TTbaqc3hmfEiu5G0J8U6ztTUMwXkBvaknE640NPgMQJqBaey0E4u0txYgyvMvvxfwtcOrDRYqYPBnA"

	// 解析JWT。
	token, err := jwt.Parse(jwtB64, jwks.Keyfunc)
	if err != nil {
		log.Fatalf("无法解析JWT。\n错误:%s", err.Error())
	}

	// 检查令牌是否有效。
	if !token.Valid {
		log.Fatalf("令牌无效。")
	}
	log.Println("令牌有效。")
}

以上是翻译好的内容,请查阅。

英文:

I've had a very similar use case recently so I read through some RFCs and authored this package: github.com/MicahParks/keyfunc.

It allows you to use the most popular JWT package, github.com/golang-jwt/jwt/v4, (formerly github.com/dgrijalva/jwt-go), to parse tokens. It can also automatically live reload the contents of your JWKS in a background goroutine.

Using your JWKS and JWT, here are two examples. The first will load the JWKS from a remote URL via HTTPS. The second will load it from static JSON.

From JWKS hosted via HTTPS

package main

import (
	&quot;context&quot;
	&quot;log&quot;
	&quot;time&quot;

	&quot;github.com/golang-jwt/jwt/v4&quot;

	&quot;github.com/MicahParks/keyfunc&quot;
)

func main() {
	// Get the JWKS URL.
	//
	// This is a sample JWKS service. Visit https://jwks-service.appspot.com/ and grab a token to test this example.
	jwksURL := &quot;https://jwks-service.appspot.com/.well-known/jwks.json&quot;

	// Create a context that, when cancelled, ends the JWKS background refresh goroutine.
	ctx, cancel := context.WithCancel(context.Background())

	// Create the keyfunc options. Use an error handler that logs. Refresh the JWKS when a JWT signed by an unknown KID
	// is found or at the specified interval. Rate limit these refreshes. Timeout the initial JWKS refresh request after
	// 10 seconds. This timeout is also used to create the initial context.Context for keyfunc.Get.
	options := keyfunc.Options{
		Ctx: ctx,
		RefreshErrorHandler: func(err error) {
			log.Printf(&quot;There was an error with the jwt.Keyfunc\nError: %s&quot;, err.Error())
		},
		RefreshInterval:   time.Hour,
		RefreshRateLimit:  time.Minute * 5,
		RefreshTimeout:    time.Second * 10,
		RefreshUnknownKID: true,
	}

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

	// Get a JWT to parse.
	jwtB64 := &quot;eyJraWQiOiJlZThkNjI2ZCIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJXZWlkb25nIiwiYXVkIjoiVGFzaHVhbiIsImlzcyI6Imp3a3Mtc2VydmljZS5hcHBzcG90LmNvbSIsImlhdCI6MTYzMTM2OTk1NSwianRpIjoiNDY2M2E5MTAtZWU2MC00NzcwLTgxNjktY2I3NDdiMDljZjU0In0.LwD65d5h6U_2Xco81EClMa_1WIW4xXZl8o4b7WzY_7OgPD2tNlByxvGDzP7bKYA9Gj--1mi4Q4li4CAnKJkaHRYB17baC0H5P9lKMPuA6AnChTzLafY6yf-YadA7DmakCtIl7FNcFQQL2DXmh6gS9J6TluFoCIXj83MqETbDWpL28o3XAD_05UP8VLQzH2XzyqWKi97mOuvz-GsDp9mhBYQUgN3csNXt2v2l-bUPWe19SftNej0cxddyGu06tXUtaS6K0oe0TTbaqc3hmfEiu5G0J8U6ztTUMwXkBvaknE640NPgMQJqBaey0E4u0txYgyvMvvxfwtcOrDRYqYPBnA&quot;

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

	// Check if the token is valid.
	if !token.Valid {
		log.Fatalf(&quot;The token is not valid.&quot;)
	}
	log.Println(&quot;The token is valid.&quot;)

	// End the background refresh goroutine when it&#39;s no longer needed.
	cancel()

	// This will be ineffectual because the line above this canceled the parent context.Context.
	// This method call is idempotent similar to context.CancelFunc.
	jwks.EndBackground()
}

From JWKS as JSON

package main

import (
	&quot;encoding/json&quot;
	&quot;log&quot;

	&quot;github.com/golang-jwt/jwt/v4&quot;

	&quot;github.com/MicahParks/keyfunc&quot;
)

func main() {
	// Get the JWKS as JSON.
	jwksJSON := json.RawMessage(`{&quot;keys&quot;:[{&quot;kty&quot;:&quot;RSA&quot;,&quot;e&quot;:&quot;AQAB&quot;,&quot;kid&quot;:&quot;ee8d626d&quot;,&quot;n&quot;:&quot;gRda5b0pkgTytDuLrRnNSYhvfMIyM0ASq2ZggY4dVe12JV8N7lyXilyqLKleD-2lziivvzE8O8CdIC2vUf0tBD7VuMyldnZruSEZWCuKJPdgKgy9yPpShmD2NyhbwQIAbievGMJIp_JMwz8MkdY5pzhPECGNgCEtUAmsrrctP5V8HuxaxGt9bb-DdPXkYWXW3MPMSlVpGZ5GiIeTABxqYNG2MSoYeQ9x8O3y488jbassTqxExI_4w9MBQBJR9HIXjWrrrenCcDlMY71rzkbdj3mmcn9xMq2vB5OhfHyHTihbUPLSm83aFWSuW9lE7ogMc93XnrB8evIAk6VfsYlS9Q&quot;},{&quot;kty&quot;:&quot;EC&quot;,&quot;crv&quot;:&quot;P-256&quot;,&quot;kid&quot;:&quot;711d48d1&quot;,&quot;x&quot;:&quot;tfXCoBU-wXemeQCkME1gMZWK0-UECCHIkedASZR0t-Q&quot;,&quot;y&quot;:&quot;9xzYtnKQdiQJHCtGwpZWF21eP1fy5x4wC822rCilmBw&quot;},{&quot;kty&quot;:&quot;EC&quot;,&quot;crv&quot;:&quot;P-384&quot;,&quot;kid&quot;:&quot;d52c9829&quot;,&quot;x&quot;:&quot;tFx6ev6eLs9sNfdyndn4OgbhV6gPFVn7Ul0VD5vwuplJLbIYeFLI6T42tTaE5_Q4&quot;,&quot;y&quot;:&quot;A0gzB8TqxPX7xMzyHH_FXkYG2iROANH_kQxBovSeus6l_QSyqYlipWpBy9BhY9dz&quot;},{&quot;kty&quot;:&quot;RSA&quot;,&quot;e&quot;:&quot;AQAB&quot;,&quot;kid&quot;:&quot;ecac72e5&quot;,&quot;n&quot;:&quot;nLbnTvZAUxdmuAbDDUNAfha6mw0fri3UpV2w1PxilflBuSnXJhzo532-YQITogoanMjy_sQ8kHUhZYHVRR6vLZRBBbl-hP8XWiCe4wwioy7Ey3TiIUYfW-SD6I42XbLt5o-47IR0j5YDXxnX2UU7-UgR_kITBeLDfk0rSp4B0GUhPbP5IDItS0MHHDDS3lhvJomxgEfoNrp0K0Fz_s0K33hfOqc2hD1tSkX-3oDTQVRMF4Nxax3NNw8-ahw6HNMlXlwWfXodgRMvj9pcz8xUYa3C5IlPlZkMumeNCFx1qds6K_eYcU0ss91DdbhhE8amRX1FsnBJNMRUkA5i45xkOIx15rQN230zzh0p71jvtx7wYRr5pdMlwxV0T9Ck5PCmx-GzFazA2X6DJ0Xnn1-cXkRoZHFj_8Mba1dUrNz-NWEk83uW5KT-ZEbX7nzGXtayKWmGb873a8aYPqIsp6bQ_-eRBd8TDT2g9HuPyPr5VKa1p33xKaohz4DGy3t1Qpy3UWnbPXUlh5dLWPKz-TcS9FP5gFhWVo-ZhU03Pn6P34OxHmXGWyQao18dQGqzgD4e9vY3rLhfcjVZJYNlWY2InsNwbYS-DnienPf1ws-miLeXxNKG3tFydoQzHwyOxG6Wc-HBfzL_hOvxINKQamvPasaYWl1LWznMps6elKCgKDc&quot;},{&quot;kty&quot;:&quot;EC&quot;,&quot;crv&quot;:&quot;P-521&quot;,&quot;kid&quot;:&quot;c570888f&quot;,&quot;x&quot;:&quot;AHNpXq0J7rikNRlwhaMYDD8LGVAVJzNJ-jEPksUIn2LB2LCdNRzfAhgbxdQcWT9ktlc9M1EhmTLccEqfnWdGL9G1&quot;,&quot;y&quot;:&quot;AfHPUW3GYzzqbTczcYR0nYMVMFVrYsUxv4uiuSNV_XRN3Jf8zeYbbOLJv4S3bUytO7qHY8bfZxPxR9nn3BBTf5ol&quot;}]}`)

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

	// Get a JWT to parse.
	jwtB64 := &quot;eyJraWQiOiJlZThkNjI2ZCIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJXZWlkb25nIiwiYXVkIjoiVGFzaHVhbiIsImlzcyI6Imp3a3Mtc2VydmljZS5hcHBzcG90LmNvbSIsImlhdCI6MTYzMTM2OTk1NSwianRpIjoiNDY2M2E5MTAtZWU2MC00NzcwLTgxNjktY2I3NDdiMDljZjU0In0.LwD65d5h6U_2Xco81EClMa_1WIW4xXZl8o4b7WzY_7OgPD2tNlByxvGDzP7bKYA9Gj--1mi4Q4li4CAnKJkaHRYB17baC0H5P9lKMPuA6AnChTzLafY6yf-YadA7DmakCtIl7FNcFQQL2DXmh6gS9J6TluFoCIXj83MqETbDWpL28o3XAD_05UP8VLQzH2XzyqWKi97mOuvz-GsDp9mhBYQUgN3csNXt2v2l-bUPWe19SftNej0cxddyGu06tXUtaS6K0oe0TTbaqc3hmfEiu5G0J8U6ztTUMwXkBvaknE640NPgMQJqBaey0E4u0txYgyvMvvxfwtcOrDRYqYPBnA&quot;

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

	// Check if the token is valid.
	if !token.Valid {
		log.Fatalf(&quot;The token is not valid.&quot;)
	}
	log.Println(&quot;The token is valid.&quot;)
}

答案3

得分: 0

我遇到了一个非常类似的用例,我想要验证/验证访问令牌并从中提取字段(例如:isssubaudexpiatjti等)。对于我的用例,我使用了jwxjwt-go库。

我尝试了@tim-cooper的示例,但它在最新的API版本中无法编译/工作,所以这里是对我有效的代码片段。

代码片段

go.mod
module my-go-module

go 1.16

require (
	github.com/dgrijalva/jwt-go v3.2.0+incompatible
	github.com/lestrrat-go/jwx v1.0.4
)
代码
package main

import (
	"errors"
	"fmt"

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

func main() {
    jwksURL := "https://your-tenant.auth0.com/.well-known/jwks.json"

	keySet, _ := jwk.Fetch(jwksURL)
	var accessToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6Ind5TXdLNEE2Q0w5UXcxMXVvZlZleVExMTlYeVgteHlreW1ra1h5Z1o1T00ifQ.eyJzdWIiOiIwMHUxOGVlaHUzNDlhUzJ5WDFkOCIsIm5hbWUiOiJva3RhcHJveHkgb2t0YXByb3h5IiwidmVyIjoxLCJpc3MiOiJodHRwczovL2NvbXBhbnl4Lm9rdGEuY29tIiwiYXVkIjoidlpWNkNwOHJuNWx4ck45YVo2ODgiLCJpYXQiOjE0ODEzODg0NTMsImV4cCI6MTQ4MTM5MjA1MywianRpIjoiSUQuWm9QdVdIR3IxNkR6a3RUbEdXMFI4b1lRaUhnVWg0aUotTHo3Z3BGcGItUSIsImFtciI6WyJwd2QiXSwiaWRwIjoiMDBveTc0YzBnd0hOWE1SSkJGUkkiLCJub25jZSI6Im4tMFM2X1d6QTJNaiIsInByZWZlcnJlZF91c2VybmFtZSI6Im9rdGFwcm94eUBva3RhLmNvbSIsImF1dGhfdGltZSI6MTQ4MTM4ODQ0MywiYXRfaGFzaCI6Im1YWlQtZjJJczhOQklIcV9CeE1ISFEifQ.OtVyCK0sE6Cuclg9VMD2AwLhqEyq2nv3a1bfxlzeS-bdu9KtYxcPSxJ6vxMcSSbMIIq9eEz9JFMU80zqgDPHBCjlOsC5SIPz7mm1Z3gCwq4zsFJ-2NIzYxA3p161ZRsPv_3bUyg9B_DPFyBoihgwWm6yrvrb4rmHXrDkjxpxCLPp3OeIpc_kb2t8r5HEQ5UBZPrsiScvuoVW13YwWpze59qBl_84n9xdmQ5pS7DklzkAVgqJT_NWBlb5uo6eW26HtJwHzss7xOIdQtcOtC1Gj3O82a55VJSQnsEEBeqG1ESb5Haq_hJgxYQnBssKydPCIxdZiye-0Ll9L8wWwpzwig"
	token, err := verify(accessToken, keySet)
	if err != nil {
		fmt.Printf("在验证访问令牌时出错:%v\n", err)
	}

    // 检查令牌是否有效。
    if !token.Valid {
        fmt.Println("令牌无效。")
    }

	// 从令牌中提取键值并将它们打印到控制台上
	claims := token.Claims.(jwt.MapClaims)
	for key, value := range claims {
		fmt.Printf("%s\t%v\n", key, value)
	}
}

func verify(tokenString string, keySet *jwk.Set) (*jwt.Token, error) {
	tkn, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if token.Method.Alg() != jwa.RS256.String() { 
			return nil, fmt.Errorf("意外的签名方法:%v", token.Header["alg"])
		}
		kid, ok := token.Header["kid"].(string)
		if !ok {
			return nil, errors.New("未找到kid头")
		}
		keys := keySet.LookupKeyID(kid)
		if len(keys) == 0 {
			return nil, fmt.Errorf("未找到密钥%v", kid)
		}
		var raw interface{}
		return raw, keys[0].Raw(&raw)
	})
	return tkn, err
}
英文:

I came across a very similar use case where I wanted to verify/validate an access-token and extract fields(such as: iss, sub, aud, exp, iat, jti, etc..) from it after parsing/decoding. For my use case, I have used jwx and jwt-go lib.

I have tried the @tim-cooper example but it did not compile/work with the latest API version, so here is the code snippet which worked for me.

Code snippet

go.mod
module my-go-module

go 1.16

require (
	github.com/dgrijalva/jwt-go v3.2.0+incompatible
	github.com/lestrrat-go/jwx v1.0.4
)
Code
package main

import (
	&quot;errors&quot;
	&quot;fmt&quot;

	&quot;github.com/dgrijalva/jwt-go&quot;
	&quot;github.com/lestrrat-go/jwx/jwa&quot;
	&quot;github.com/lestrrat-go/jwx/jwk&quot;
)

func main() {
    jwksURL := &quot;https://your-tenant.auth0.com/.well-known/jwks.json&quot;

	keySet, _ := jwk.Fetch(jwksURL)
	var accessToken = &quot;eyJhbGciOiJSUzI1NiIsImtpZCI6Ind5TXdLNEE2Q0w5UXcxMXVvZlZleVExMTlYeVgteHlreW1ra1h5Z1o1T00ifQ.eyJzdWIiOiIwMHUxOGVlaHUzNDlhUzJ5WDFkOCIsIm5hbWUiOiJva3RhcHJveHkgb2t0YXByb3h5IiwidmVyIjoxLCJpc3MiOiJodHRwczovL2NvbXBhbnl4Lm9rdGEuY29tIiwiYXVkIjoidlpWNkNwOHJuNWx4ck45YVo2ODgiLCJpYXQiOjE0ODEzODg0NTMsImV4cCI6MTQ4MTM5MjA1MywianRpIjoiSUQuWm9QdVdIR3IxNkR6a3RUbEdXMFI4b1lRaUhnVWg0aUotTHo3Z3BGcGItUSIsImFtciI6WyJwd2QiXSwiaWRwIjoiMDBveTc0YzBnd0hOWE1SSkJGUkkiLCJub25jZSI6Im4tMFM2X1d6QTJNaiIsInByZWZlcnJlZF91c2VybmFtZSI6Im9rdGFwcm94eUBva3RhLmNvbSIsImF1dGhfdGltZSI6MTQ4MTM4ODQ0MywiYXRfaGFzaCI6Im1YWlQtZjJJczhOQklIcV9CeE1ISFEifQ.OtVyCK0sE6Cuclg9VMD2AwLhqEyq2nv3a1bfxlzeS-bdu9KtYxcPSxJ6vxMcSSbMIIq9eEz9JFMU80zqgDPHBCjlOsC5SIPz7mm1Z3gCwq4zsFJ-2NIzYxA3p161ZRsPv_3bUyg9B_DPFyBoihgwWm6yrvrb4rmHXrDkjxpxCLPp3OeIpc_kb2t8r5HEQ5UBZPrsiScvuoVW13YwWpze59qBl_84n9xdmQ5pS7DklzkAVgqJT_NWBlb5uo6eW26HtJwHzss7xOIdQtcOtC1Gj3O82a55VJSQnsEEBeqG1ESb5Haq_hJgxYQnBssKydPCIxdZiye-0Ll9L8wWwpzwig&quot;
	token, err := verify(accessToken, keySet)
	if err != nil {
		fmt.Printf(&quot;Gor an error while verifiying access token: %v\n&quot;, err)
	}

    // Check if the token is valid.
    if !token.Valid {
        fmt.Println(&quot;The token is not valid.&quot;)
    }

	// Extract key value from the token and print them on console
	claims := token.Claims.(jwt.MapClaims)
	for key, value := range claims {
		fmt.Printf(&quot;%s\t%v\n&quot;, key, value)
	}
}

func verify(tokenString string, keySet *jwk.Set) (*jwt.Token, error) {
	tkn, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if token.Method.Alg() != jwa.RS256.String() { 
			return nil, fmt.Errorf(&quot;unexpected signing method: %v&quot;, token.Header[&quot;alg&quot;])
		}
		kid, ok := token.Header[&quot;kid&quot;].(string)
		if !ok {
			return nil, errors.New(&quot;kid header not found&quot;)
		}
		keys := keySet.LookupKeyID(kid)
		if len(keys) == 0 {
			return nil, fmt.Errorf(&quot;key %v not found&quot;, kid)
		}
		var raw interface{}
		return raw, keys[0].Raw(&amp;raw)
	})
	return tkn, err
}

huangapple
  • 本文由 发表于 2016年12月11日 00:55:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/41077953.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定