使用Go Fiber中间件进行AWS Cognito JWT验证(出现“key is of invalid type”错误)

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

AWS Cognito JWT verification using Go Fiber middleware (getting "key is of invalid type")

问题

当我尝试在中间件中验证基于Cognito的JWT时,我遇到了"key is of invalid type"的错误。目前,我在设置Fiber应用程序时设置了中间件,代码如下:

// 从本地读取我从AWS获取的 "jwks.json" 文件
signingKey, err := ioutil.ReadFile("./jwks.json")
if err != nil {
    log.Fatal("Error when opening file: ", err)
}

// 在创建中间件时传入签名密钥
app.Get("/api", middleware.Protected(signingKey), handlers.ReadSomeData)

然后,我的中间件如下所示,其中大部分代码来自Go Fiber的JWT示例仓库:

func Protected(signingKey []byte) func(*fiber.Ctx) error {
    return jwtware.New(jwtware.Config{
        SigningKey:    signingKey,
        ErrorHandler:  jwtError,
        SigningMethod: "RS256",
    })
}

func jwtError(c *fiber.Ctx, err error) error {
    if err.Error() == "Missing or malformed JWT" {
        c.Status(fiber.StatusBadRequest)
        return c.JSON(fiber.Map{"status": "error", "message": err.Error(), "data": nil})
    } else {
        c.Status(fiber.StatusUnauthorized)
        return c.JSON(fiber.Map{"status": "error", "message": err.Error(), "data": nil})
    }
}

在得到答案后,我尝试使用"SigningKeys"参数,但出现了类型不匹配的问题,所以我最终通过以下方式读取了jwks json文件:

func Protected() func(*fiber.Ctx) error {
    signingKey, err := os.ReadFile("./jwks.json")
    if err != nil {
        // 处理错误
    }

    x := make(map[string]interface{})
    json.Unmarshal(signingKey, &x)

    return jwtware.New(jwtware.Config{
        SigningKeys:   x,
        ErrorHandler:  jwtError,
        SigningMethod: "RS256",
    })
}

然而,现在我遇到了"Unexpected jwt key id=XXXXXXXXXXXX"的错误。

英文:

I am getting "key is of invalid type" when I try to verify a Cognito based JWT in my middleware. Currently I set up the middle ware like this when the Fiber app is being setup:

// read the "jwks.json" that I got from AWS locally
signingKey, err := ioutil.ReadFile("./jwks.json")
if err != nil {
	log.Fatal("Error when opening file: ", err)
}

// pass in the signing key when middle ware is created
app.Get("/api", middleware.Protected(signingKey), handlers.ReadSomeData)

Then my middleware looks like this where most of it is from Go Fiber's JWT example repo.

func Protected(signingKey []byte) func(*fiber.Ctx) error {
	return jwtware.New(jwtware.Config{
		SigningKey:    signingKey,
		ErrorHandler:  jwtError,
		SigningMethod: "RS256",
	})
}

func jwtError(c *fiber.Ctx, err error) error {
	if err.Error() == "Missing or malformed JWT" {
		c.Status(fiber.StatusBadRequest)
		return c.JSON(fiber.Map{"status": "error", "message": err.Error(), "data": nil})

	} else {
		c.Status(fiber.StatusUnauthorized)
		return c.JSON(fiber.Map{"status": "error", "message": err.Error(), "data": nil})
	}
}

After an answer, I tried using the "SigningKeys" param but there was a type mismatch so I ended up reading in the jwks json file like so:

func Protected() func(*fiber.Ctx) error {

	signingKey, err := os.ReadFile("./jwks.json")
	if err != nil {

	}

	x := make(map[string]interface{})

	json.Unmarshal(signingKey, &x)

	return jwtware.New(jwtware.Config{
		SigningKeys:   x,
		ErrorHandler:  jwtError,
		SigningMethod: "RS256",
	})
}

However now my error is "Unexpected jwt key id=XXXXXXXXXXXX"

答案1

得分: 1

原文翻译如下:

原来,Fiber框架内置了一个功能,可以通过提供密钥的URL来获取jwks.json数据。可能还有一种方法可以加载本地文件,但是通常不建议这样做,因为密钥可能会根据所处的环境(生产环境或测试环境)而变化。

您需要知道您的AWS用户池区域和该用户池的ID。通常在用户池设置视图中提供这些信息,但您也可以根据AWS文档中提供的以下示例轻松获取这些信息:

https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json

更多信息请参见:AWS:验证JSON Web Token

以下是一个简单的示例,演示如何与AWS Cognito JWT URL配合使用:

	authMiddleware := jwtware.New(jwtware.Config{
		TokenLookup: "header:Authorization",
		AuthScheme:  "Bearer",
		KeySetURLs: []string{
			"https://cognito-idp.some-region-1.amazonaws.com/some-region-1_MYUserPoolId/.well-known/jwks.json",
		},
	})

	// 匹配任何路由
	app.Use(authMiddleware, func(c *fiber.Ctx) error {
		return c.SendString("🎉 Yay!")
	})

	log.Fatal(app.Listen(":3000"))

现在,您可以使用类似以下的请求进行测试:

curl --location --request GET 'http://127.0.0.1:3000' \
--header 'Authorization: Bearer MyAWSJWTToken..'

或者使用任何HTTP客户端(如Postman)。您必须在Authorization头中提供您的JWT。

另请参阅:

英文:

It turns out fiber has a built-in functionality to pull the jwks.json data if you provide it a url to the keys. Probably there is also a method to make it load a local file, but with AWS keys you usually do not do it - keys may chnage depending on the environment you are in - production or test.

You need to know your AWS user pool region and that user pool's ID. That is normally provided in the user pool settings view, but you can easily come up with it based on the following example provided in AWS documentation:

https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json

For more see: AWS: Verifying a JSON web token

Here's a simple example to get it working with AWS Cognito JWT url:

	authMiddleware := jwtware.New(jwtware.Config{
		TokenLookup: "header:Authorization",
		AuthScheme:  "Bearer",
		KeySetURLs: []string{
			"https://cognito-idp.some-region-1.amazonaws.com/some-region-1_MYUserPoolId/.well-known/jwks.json",
		},
	})

	// Match any route
	app.Use(authMiddleware, func(c *fiber.Ctx) error {
		return c.SendString("🥇 Yay!")
	})

	log.Fatal(app.Listen(":3000"))

You should now be able to test it with a request like this:

curl --location --request GET 'http://127.0.0.1:3000' \
--header 'Authorization: Bearer MyAWSJWTToken..'

Or with any HTTP client(like Postman). You must provide your JWT in a Authorization header.

See also:

答案2

得分: 0

github.com/gofiber/jwt项目使用github.com/MicahParks/keyfunc作为JWK Set客户端。自从github.com/MicahParks/keyfunc处于预发布状态以来,该项目未进行更新。鉴于已知的错误,我建议不要在当前状态下使用该项目。

以下是从AWS Cognito解析JWK Set并直接使用该集合中的密钥解析JWT的示例,该示例来自github.com/MicahParks/keyfunc项目:

这是来自github.com/MicahParks/keyfunc项目的示例链接:链接

package main

import (
	"fmt"
	"log"
	"time"

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

	"github.com/MicahParks/keyfunc"
)

func main() {
	// 从AWS区域和userPoolId获取JWKS URL。
	//
	// 在此处查看AWS文档:
	// https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
	regionID := ""   // TODO 获取AWS Cognito实例的区域ID。
	userPoolID := "" // TODO 获取AWS Cognito实例的用户池ID。
	jwksURL := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", regionID, userPoolID)

	// 创建keyfunc选项。使用记录错误的错误处理程序。当发现由未知KID签名的JWT或在指定的间隔时刷新JWKS。限制刷新速率。在初始JWKS刷新请求超时后,超时时间为10秒。此超时时间也用于创建keyfunc.Get的初始context.Context。
	options := keyfunc.Options{
		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 := "eyJraWQiOiJmNTVkOWE0ZSIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJLZXNoYSIsImF1ZCI6IlRhc2h1YW4iLCJpc3MiOiJqd2tzLXNlcnZpY2UuYXBwc3BvdC5jb20iLCJleHAiOjE2MTkwMjUyMTEsImlhdCI6MTYxOTAyNTE3NywianRpIjoiMWY3MTgwNzAtZTBiOC00OGNmLTlmMDItMGE1M2ZiZWNhYWQwIn0.vetsI8W0c4Z-bs2YCVcPb9HsBm1BrMhxTBSQto1koG_lV-2nHwksz8vMuk7J7Q1sMa7WUkXxgthqu9RGVgtGO2xor6Ub0WBhZfIlFeaRGd6ZZKiapb-ASNK7EyRIeX20htRf9MzFGwpWjtrS5NIGvn1a7_x9WcXU9hlnkXaAWBTUJ2H73UbjDdVtlKFZGWM5VGANY4VG7gSMaJqCIKMxRPn2jnYbvPIYz81sjjbd-sc2-ePRjso7Rk6s382YdOm-lDUDl2APE-gqkLWdOJcj68fc6EBIociradX_ADytj-JYEI6v0-zI-8jSckYIGTUF5wjamcDfF5qyKpjsmdrZJA"

	// 解析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。
	jwks.EndBackground()
}
英文:

The github.com/gofiber/jwt project uses github.com/MicahParks/keyfunc as a JWK Set client. It also has not updated it since github.com/MicahParks/keyfunc was in pre-release. I would recommend not using that project in its current state due to know bugs.

Here's an example of parsing a JWK Set from AWS Cognito and then using the keys in that set to parse a JWT directly from the github.com/Micahparks/keyfunc project:

Here's a link to this example from the github.com/MicahParks/keyfunc project: link

package main

import (
	"fmt"
	"log"
	"time"

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

	"github.com/MicahParks/keyfunc"
)

func main() {
	// Get the JWKS URL from your AWS region and userPoolId.
	//
	// See the AWS docs here:
	// https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
	regionID := ""   // TODO Get the region ID for your AWS Cognito instance.
	userPoolID := "" // TODO Get the user pool ID of your AWS Cognito instance.
	jwksURL := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", regionID, userPoolID)

	// 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{
		RefreshErrorHandler: func(err error) {
			log.Printf("There was an error with the jwt.Keyfunc\nError: %s", 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("Failed to create JWKS from resource at the given URL.\nError: %s", err.Error())
	}

	// Get a JWT to parse.
	jwtB64 := "eyJraWQiOiJmNTVkOWE0ZSIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJLZXNoYSIsImF1ZCI6IlRhc2h1YW4iLCJpc3MiOiJqd2tzLXNlcnZpY2UuYXBwc3BvdC5jb20iLCJleHAiOjE2MTkwMjUyMTEsImlhdCI6MTYxOTAyNTE3NywianRpIjoiMWY3MTgwNzAtZTBiOC00OGNmLTlmMDItMGE1M2ZiZWNhYWQwIn0.vetsI8W0c4Z-bs2YCVcPb9HsBm1BrMhxTBSQto1koG_lV-2nHwksz8vMuk7J7Q1sMa7WUkXxgthqu9RGVgtGO2xor6Ub0WBhZfIlFeaRGd6ZZKiapb-ASNK7EyRIeX20htRf9MzFGwpWjtrS5NIGvn1a7_x9WcXU9hlnkXaAWBTUJ2H73UbjDdVtlKFZGWM5VGANY4VG7gSMaJqCIKMxRPn2jnYbvPIYz81sjjbd-sc2-ePRjso7Rk6s382YdOm-lDUDl2APE-gqkLWdOJcj68fc6EBIociradX_ADytj-JYEI6v0-zI-8jSckYIGTUF5wjamcDfF5qyKpjsmdrZJA"

	// 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.")

	// End the background refresh goroutine when it's no longer needed.
	jwks.EndBackground()
}

huangapple
  • 本文由 发表于 2022年9月12日 20:50:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/73689595.html
匿名

发表评论

匿名网友

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

确定