You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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"
}

问题排查与解决办法

  1. 修正JWKS端点地址
    你使用的common通用端点会返回所有Azure AD租户的密钥,范围过广且不一定包含你令牌对应的密钥。需要替换为你的租户ID专属端点,格式为https://login.microsoftonline.com/{你的租户ID}/discovery/keys。

  2. 统一JWT库并添加核心声明验证
    混用dgrijalva/jwt-go和lestrrat-go/jwx两个库易引发兼容问题,建议统一使用lestrrat-go/jwx。同时必须验证aud(受众,即你的应用客户端ID)和iss(颁发者,格式为https://sts.windows.net/{你的租户ID}/)这两个核心声明,确保令牌是发给你的应用的合法令牌。

  3. 修正后的验证代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 09:07:54