英文:
Parse/Validate JWT token from AzureAD in golang
问题
我已经在Azure AD中设置了OAuth2,并让它为我的Web应用程序发行JWT。在后续的请求中,我想要验证发行的JWT。我正在使用github.com/dgrijalva/jwt-go
来进行验证,但它总是失败。
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte("bW8ZcMjBCnJZS-ibX5UQDNStvx4"), nil
})
if err != nil {
return nil, err
}
我从这里的公钥列表中随机选择了kid
声明:https://login.microsoftonline.com/common/discovery/v2.0/keys,所以我很困惑为什么这不起作用。
有人之前做过这个或者有什么指导吗?
英文:
I have Azure AD setup with OAuth2 and have it issuing a JWT for my web app. On subsequent requests, I want to validate the JWT that was issued. I'm using github.com/dgrijalva/jwt-go
to do so however it always fails.
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte("bW8ZcMjBCnJZS-ibX5UQDNStvx4"), nil
})
if err != nil {
return nil, err
}
I'm picking at random the kid
claim from the public keys listed by MS here https://login.microsoftonline.com/common/discovery/v2.0/keys so I'm lost as this isn't working.
Has anyone done this before or have any pointers?
答案1
得分: 1
位于 https://login.microsoftonline.com/common/discovery/v2.0/keys
的资源是一个 JWKS(JSON Web Key Set),如果你只想验证由该服务签名的令牌,你可以使用下面的代码片段。我为这个用例编写了一个专门的包:github.com/MicahParks/keyfunc
在底层,该包将读取和解析 JWKS 中找到的加密密钥,然后根据密钥的 Key ID(kid
)将 JWT 与这些密钥关联起来。它还具有一些逻辑,用于自动刷新远程 JWKS 资源。
package main
import (
"context"
"log"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/MicahParks/keyfunc"
)
func main() {
// 获取 JWKS URL。
jwksURL := "https://login.microsoftonline.com/common/discovery/v2.0/keys"
// 创建一个上下文,当取消时,结束 JWKS 后台刷新 goroutine。
ctx, cancel := context.WithCancel(context.Background())
// 创建 keyfunc 选项。使用一个记录日志的错误处理程序。当发现由未知 KID 签名的 JWT 或在指定的间隔时间内刷新 JWKS 时,刷新 JWKS。限制刷新速率。在初始 JWKS 刷新请求超时后,设置初始 context.Context 的超时时间为 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。
//
// 这个 JWT 没有由 Azure AD 签名。
jwtB64 := "eyJraWQiOiJlZThkNjI2ZCIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJXZWlkb25nIiwiYXVkIjoiVGFzaHVhbiIsImlzcyI6Imp3a3Mtc2VydmljZS5hcHBzcG90LmNvbSIsImlhdCI6MTYzMTM2OTk1NSwianRpIjoiNDY2M2E5MTAtZWU2MC00NzcwLTgxNjktY2I3NDdiMDljZjU0In0.LwD65d5h6U_2Xco81EClMa_1WIW4xXZl8o4b7WzY_7OgPD2tNlByxvGDzP7bKYA9Gj--1mi4Q4li4CAnKJkaHRYB17baC0H5P9lKMPuA6AnChTzLafY6yf-YadA7DmakCtIl7FNcFQQL2DXmh6gS9J6TluFoCIXj83MqETbDWpL28o3XAD_05UP8VLQzH2XzyqWKi97mOuvz-GsDp9mhBYQUgN3csNXt2v2l-bUPWe19SftNej0cxddyGu06tXUtaS6K0oe0TTbaqc3hmfEiu5G0J8U6ztTUMwXkBvaknE640NPgMQJqBaey0E4u0txYgyvMvvxfwtcOrDRYqYPBnA"
// 解析 JWT。
var token *jwt.Token
if token, err = jwt.Parse(jwtB64, jwks.Keyfunc); err != nil {
log.Fatalf("无法解析 JWT。\n错误:%s", err.Error())
}
// 检查令牌是否有效。
if !token.Valid {
log.Fatalf("令牌无效。")
}
log.Println("令牌有效。")
// 当不再需要后台刷新 goroutine 时,结束它。
cancel()
// 这将无效,因为上面的代码已经取消了父 context.Context。
// 这个方法调用是幂等的,类似于 context.CancelFunc。
jwks.EndBackground()
}
英文:
The asset located at https://login.microsoftonline.com/common/discovery/v2.0/keys
is what's known as a JWKS, JSON Web Key Set. If all you want to do is authenticate tokens signed by this service, you can use something similar to the below code snippet. I've authored a package just for this use case: github.com/MicahParks/keyfunc
Under the hood, this package will read and parse the cryptographic keys found in the JWKS, then associate JWTs with those keys based on their key ID, kid
. It also has some logic around automatically refreshing a remote JWKS resource.
package main
import (
"context"
"log"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/MicahParks/keyfunc"
)
func main() {
// Get the JWKS URL.
jwksURL := "https://login.microsoftonline.com/common/discovery/v2.0/keys"
// 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("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.
//
// This wasn't signed by Azure AD.
jwtB64 := "eyJraWQiOiJlZThkNjI2ZCIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJXZWlkb25nIiwiYXVkIjoiVGFzaHVhbiIsImlzcyI6Imp3a3Mtc2VydmljZS5hcHBzcG90LmNvbSIsImlhdCI6MTYzMTM2OTk1NSwianRpIjoiNDY2M2E5MTAtZWU2MC00NzcwLTgxNjktY2I3NDdiMDljZjU0In0.LwD65d5h6U_2Xco81EClMa_1WIW4xXZl8o4b7WzY_7OgPD2tNlByxvGDzP7bKYA9Gj--1mi4Q4li4CAnKJkaHRYB17baC0H5P9lKMPuA6AnChTzLafY6yf-YadA7DmakCtIl7FNcFQQL2DXmh6gS9J6TluFoCIXj83MqETbDWpL28o3XAD_05UP8VLQzH2XzyqWKi97mOuvz-GsDp9mhBYQUgN3csNXt2v2l-bUPWe19SftNej0cxddyGu06tXUtaS6K0oe0TTbaqc3hmfEiu5G0J8U6ztTUMwXkBvaknE640NPgMQJqBaey0E4u0txYgyvMvvxfwtcOrDRYqYPBnA"
// Parse the JWT.
var token *jwt.Token
if token, err = jwt.Parse(jwtB64, jwks.Keyfunc); 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.
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()
}
答案2
得分: 0
非常让人恼火的是,这是一个Azure AD配置问题,开箱即用的配置会生成一个用于MS Graph的JWT令牌,整个身份验证过程成功,但是当您尝试验证令牌时,由于某种原因失败。一旦您正确设置了Azure AD以适用于您的应用程序,并具有正确的范围,它将正确验证。我在这里详细介绍了具体情况-https://blog.jonathanchannon.com/2022-01-29-azuread-golang/
英文:
Annoyingly it was a Azure AD config issue and out of the box it will generate a JWT token for MS Graph and the whole auth process succeeds but when you try to validate the token it fails for some reason. Once you have setup Azure AD correctly for your app with a correct scope it validates properly. I blogged about the specifics here - https://blog.jonathanchannon.com/2022-01-29-azuread-golang/
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论