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

Go中JWT Payload的Base64编码与jwt.io结果不一致问题

解决Go手动编码JWT与jwt.io结果不一致的问题

你的猜测完全正确,问题核心就是JSON序列化的格式差异导致的,具体来说是JSON中的空格让Base64编码结果不同,最终使得HMAC签名的输入不一致,签名自然就不匹配了。

问题根源

JWT规范明确要求Header和Payload必须是无额外空格、换行的紧凑格式JSON,再经过URL安全的Base64编码(也就是Go里的base64.RawURLEncoding)。

你测试用例里的Payload是带空格的:

{"exp": 4724377561}

而jwt.io生成的是完全紧凑无空格的版本:

{"exp":4724377561}

这两个JSON语义相同,但字节内容完全不一样,所以Base64编码结果差异明显:

  • 带空格的编码结果:ewogICAiZXhwIjogNDcyNDM3NzU2MQp9
  • 紧凑格式的编码结果:eyJleHAiOjQ3MjQzNzc1NjF9(和jwt.io输出一致)

签名是对HeaderBase64.PayloadBase64这个拼接字符串做HMAC运算,输入不一样,签名值肯定就对不上了。

解决方案

有两种方式可以解决这个问题,你可以根据测试可读性的需求自由选择:

方案1:直接修改测试用例为紧凑JSON

最简单的方式就是把测试用例里的Header和Payload都改成无空格的紧凑格式,这样直接转Base64就能和jwt.io的结果完全对齐:

var testData = []struct {
	name        string
	header      string
	payload     string
	signature   string
	pass        bool
	errorType   reflect.Type
}{{
	name:      "Succeed if token not expired",
	header:    `{"typ":"JWT","alg":"HS256"}`, // 去掉不必要的空格
	payload:   `{"exp":4724377561}`, // 去掉冒号后的空格
	signature: "JHtMKvPSMa5BD22BsbxiP1-ELRh1XkIKkarRSev0ZjU",
	pass:      true,
}}

这样构造出来的jwt64就和jwt.io生成的完全一致,签名验证自然能通过。

方案2:保持测试用例的可读格式,编码前先序列化为紧凑JSON

如果你想保留测试用例中JSON的可读性(带空格、换行),可以在编码前先把JSON解析成Go数据结构,再重新序列化为紧凑JSON:

首先需要导入encoding/json包,然后修改TestParseJwt中构造jwt64的逻辑:

import (
	"encoding/base64"
	"encoding/json"
	"reflect"
	"testing"
)

// ... 你的testData定义不变 ...

func TestParseJwt(t *testing.T) {
	HmacSecret = []byte("My super secret key")
	for _, tst := range testData {
		// 处理Header:解析后重新序列化为紧凑格式
		var headerMap map[string]interface{}
		if err := json.Unmarshal([]byte(tst.header), &headerMap); err != nil {
			t.Fatalf("%s: failed to parse header: %v", tst.name, err)
		}
		compactHeader, err := json.Marshal(headerMap)
		if err != nil {
			t.Fatalf("%s: failed to marshal header: %v", tst.name, err)
		}

		// 处理Payload:同样解析后重新序列化
		var payloadMap map[string]interface{}
		if err := json.Unmarshal([]byte(tst.payload), &payloadMap); err != nil {
			t.Fatalf("%s: failed to parse payload: %v", tst.name, err)
		}
		compactPayload, err := json.Marshal(payloadMap)
		if err != nil {
			t.Fatalf("%s: failed to marshal payload: %v", tst.name, err)
		}

		// 构造符合规范的JWT字符串
		jwt64 := base64.RawURLEncoding.EncodeToString(compactHeader) + "." + 
			base64.RawURLEncoding.EncodeToString(compactPayload) + "." + 
			tst.signature
		
		_, err := ParseJwt(jwt64)
		if tst.pass {
			if err != nil {
				t.Fatal(tst.name, err)
			}
		} else {
			if reflect.TypeOf(err).String() != tst.errorType.String() {
				t.Fatal(tst.name, err)
			}
		}
	}
}

这种方式既保留了测试用例的可读性,又能确保生成符合JWT规范的紧凑JSON,和jwt.io的编码结果完全一致。

关键提醒

JWT的签名计算对输入的字节内容极度敏感,哪怕是一个空格的差异都会导致签名完全不同。所以手动实现JWT相关逻辑时,一定要严格遵循规范:

  • 必须使用紧凑格式JSON
  • 必须使用base64.RawURLEncoding(不带填充字符的URL安全Base64)

内容的提问来源于stack exchange,提问作者Arne Winter

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:04:31