Go中Azure JWT令牌验证失败问题排查求助
使用Azure JWT令牌保护Go HTTP路由时的验证问题
我有一个Go HTTP服务器,想要用Azure JWT令牌保护路由,能够生成令牌但无法完成验证操作。以下是我的实现代码:
package main import ( "context" "errors" "fmt" "github.com/dgrijalva/jwt-go" "github.com/lestrrat-go/jwx/jwa" "github.com/lestrrat-go/jwx/jwk" njwt "github.com/lestrrat-go/jwx/jwt" ) const token = "<access-token>" const jwksURL = `https://login.microsoftonline.com/common/discovery/keys` func main() { set, _ := jwk.Fetch(context.TODO(), jwksURL) // verified that set has required kid verify2(token, set) token, err := verify(token, set) // 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) } } func verify2(token string, keyset jwk.Set) { btoken := []byte(token) parsedToken, err := njwt.Parse( btoken, //token is a []byte njwt.WithKeySet(keyset), njwt.WithValidate(true), ) fmt.Printf("%v %v", parsedToken, err) } 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("unexpected signing method: %v", token.Header["alg"]) } kid, ok := token.Header["kid"].(string) if !ok { return nil, errors.New("kid header not found") } keys, ok := keySet.LookupKeyID(kid) if !ok { return nil, fmt.Errorf("key %v not found", kid) } var raw interface{} err := keys.Raw(&raw) return raw, err }) return tkn, err }
调用verify2(..)时返回错误:<nil> failed to match any of the keys,调用verify(..)时返回错误:crypto/rsa: verification error。我的JWT头信息如下:
{ "typ": "JWT", "nonce": "...", "alg": "RS256", "x5t": "-KI3Q9nNR7bRofxmeZoXqbHZGew", "kid": "-KI3Q9nNR7bRofxmeZoXqbHZGew" }
问题排查与解决办法
修正JWKS端点地址
你使用的common通用端点会返回所有Azure AD租户的密钥,范围过广且不一定包含你令牌对应的密钥。需要替换为你的租户ID专属端点,格式为https://login.microsoftonline.com/{你的租户ID}/discovery/keys。统一JWT库并添加核心声明验证
混用dgrijalva/jwt-go和lestrrat-go/jwx两个库易引发兼容问题,建议统一使用lestrrat-go/jwx。同时必须验证aud(受众,即你的应用客户端ID)和iss(颁发者,格式为https://sts.windows.net/{你的租户ID}/)这两个核心声明,确保令牌是发给你的应用的合法令牌。修正后的验证代码
package main import ( "context" "fmt" "log" "github.com/lestrrat-go/jwx/jwk" "github.com/lestrrat-go/jwx/jwt" ) const token = "<access-token>" // 替换成你的Azure AD租户ID const tenantID = "your-tenant-id" // 替换成你的应用注册客户端ID const clientID = "your-app-client-id" func main() { jwksURL := fmt.Sprintf("https://login.microsoftonline.com/%s/discovery/keys", tenantID) keySet, err := jwk.Fetch(context.TODO(), jwksURL) if err != nil { log.Fatalf("获取JWKS密钥集失败: %v", err) } parsedToken, err := jwt.Parse( []byte(token), jwt.WithKeySet(keySet), jwt.WithValidate(true), jwt.WithAudience(clientID), jwt.WithIssuer(fmt.Sprintf("https://sts.windows.net/%s/", tenantID)), ) if err != nil { log.Fatalf("令牌验证失败: %v", err) } // 输出所有自定义声明 for key, val := range parsedToken.PrivateClaims() { fmt.Printf("%s: %v\n", key, val) } // 输出标准声明示例 fmt.Printf("\n过期时间: %v\n", parsedToken.Expiration()) fmt.Printf("颁发时间: %v\n", parsedToken.IssuedAt()) }
额外注意点
- 确认你使用的是访问令牌(Access Token),ID令牌的受众和验证逻辑与访问令牌不同。
- 建议缓存JWKS密钥集,不要每次验证都请求Azure服务器,避免性能损耗和请求限制。
jwx库的WithValidate(true)会自动验证令牌的exp(过期时间)和nbf(生效时间),无需手动处理。
内容的提问来源于stack exchange,提问作者Sahith Vibudhi
相关产品推荐
相关产品推荐

