正确编码 JWT

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

Encode JWT properly

问题

我正在尝试编写一个简单的JWT实现,具有以下功能:

  • 使用HMAC生成令牌
  • 验证令牌(检查签名是否正确或exp是否超时)
  • 解码令牌并获取声明

我想从头开始实现它,以更好地理解其深层原理。

到目前为止,我找到了这篇文章如何从头开始构建一个使用Golang的身份验证微服务。其中的一章专门介绍了如何从头开始实现JWT。我使用它来生成令牌,但是当我将令牌粘贴到https://jwt.io时,我得到了无效的签名和以下警告:

  • 警告:看起来你的JWT签名没有使用base64url正确编码(https://tools.ietforg/html/rfc4648#section-5)。请注意,根据https://tools.ietf.org/html/rfc7515#section-2,必须省略填充符号("=")。

  • 警告:看起来你的JWT头部没有使用base64url正确编码(https://tools.ietforg/html/rfc4648#section-5)。请注意,根据https://tools.ietf.org/html/rfc7515#section-2,必须省略填充符号("=")。

  • 警告:看起来你的JWT负载没有使用base64url正确编码(https://tools.ietforg/html/rfc4648#section-5)。请注意,根据https://tools.ietf.org/html/rfc7515#section-2,必须省略填充符号("=")。

我粘贴的令牌如下所示:
eyAiYWxnIjogIkhTMjU2IiwgInR5cCI6ICJKV1QiIH0=.eyJhdWQiOiJmcm9udGVuZC5rbm93c2VhcmNoLm1sIiwiZXhwIjoiMTY1MTIyMjcyMyIsImlzcyI6Imtub3dzZWFyY2gubWwifQ==.SqCW8Hxakzck9Puzl0BEOkREPDyl38g2Fd4KFaDazV4=

我的JWT代码实现:

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strings"
	"time"
)

func GenerateToken(header string, payload map[string]string, secret string) (string, error) {
	h := hmac.New(sha256.New, []byte(secret))
	header64 := base64.StdEncoding.EncodeToString([]byte(header))

	payloadstr, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	payload64 := base64.StdEncoding.EncodeToString(payloadstr)

	message := header64 + "." + payload64

	unsignedStr := header + string(payloadstr)

	h.Write([]byte(unsignedStr))
	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))

	tokenStr := message + "." + signature
	return tokenStr, nil
}

func ValidateToken(token string, secret string) (bool, error) {
	splitToken := strings.Split(token, ".")

	if len(splitToken) != 3 {
		return false, nil
	}

	header, err := base64.StdEncoding.DecodeString(splitToken[0])
	if err != nil {
		return false, err
	}
	payload, err := base64.StdEncoding.DecodeString(splitToken[1])
	if err != nil {
		return false, err
	}

	unsignedStr := string(header) + string(payload)
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(unsignedStr))

	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
	fmt.Println(signature)

	if signature != splitToken[2] {
		return false, nil
	}

	return true, nil
}

func main() {
	claimsMap := map[string]string{
		"aud": "frontend.knowsearch.ml",
		"iss": "knowsearch.ml",
		"exp": fmt.Sprint(time.Now().Add(time.Second * 2).Unix()),
	}
	secret := "Secure_Random_String"
	header := `{ "alg": "HS256", "typ": "JWT" }`

	tokenString, err := GenerateToken(header, claimsMap, secret)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("token: ", tokenString)

	isValid, _ := ValidateToken(tokenString, secret)
	fmt.Println("is token valid: ", isValid)

	duration := time.Second * 4
	time.Sleep(duration)

	isValid, _ = ValidateToken(tokenString, secret)
	fmt.Println("is token valid: ", isValid)

}

上述实现有什么问题,如何修复并消除警告?

我决定使用Golang进行实现,但是其他语言的示例也非常欢迎。

英文:

I'm trying to write simple JWT implementation with these functionalities:

  • Generating token using HMAC
  • Validating token (if signature is correct or exp is not timed out)
  • Decode token and getting claims

from scratch for better understanding how does it work in depth.

So far I found this article how to build an authentication microservice in golang from scratch. One chapter is dedicated to implementation JWT from scratch. I used it go generate token, however when I paste token in https://jwt.io I've got invalid signature and following warnings:

Token I paste look like below:
eyAiYWxnIjogIkhTMjU2IiwgInR5cCI6ICJKV1QiIH0=.eyJhdWQiOiJmcm9udGVuZC5rbm93c2VhcmNoLm1sIiwiZXhwIjoiMTY1MTIyMjcyMyIsImlzcyI6Imtub3dzZWFyY2gubWwifQ==.SqCW8Hxakzck9Puzl0BEOkREPDyl38g2Fd4KFaDazV4=

My JWT code implementation:

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strings"
	"time"
)

func GenerateToken(header string, payload map[string]string, secret string) (string, error) {
	h := hmac.New(sha256.New, []byte(secret))
	header64 := base64.StdEncoding.EncodeToString([]byte(header))

	payloadstr, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	payload64 := base64.StdEncoding.EncodeToString(payloadstr)

	message := header64 + "." + payload64

	unsignedStr := header + string(payloadstr)

	h.Write([]byte(unsignedStr))
	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))

	tokenStr := message + "." + signature
	return tokenStr, nil
}

func ValidateToken(token string, secret string) (bool, error) {
	splitToken := strings.Split(token, ".")

	if len(splitToken) != 3 {
		return false, nil
	}

	header, err := base64.StdEncoding.DecodeString(splitToken[0])
	if err != nil {
		return false, err
	}
	payload, err := base64.StdEncoding.DecodeString(splitToken[1])
	if err != nil {
		return false, err
	}

	unsignedStr := string(header) + string(payload)
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(unsignedStr))

	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
	fmt.Println(signature)

	if signature != splitToken[2] {
		return false, nil
	}

	return true, nil
}

func main() {
	claimsMap := map[string]string{
		"aud": "frontend.knowsearch.ml",
		"iss": "knowsearch.ml",
		"exp": fmt.Sprint(time.Now().Add(time.Second * 2).Unix()),
	}
	secret := "Secure_Random_String"
	header := `{ "alg": "HS256", "typ": "JWT" }`

	tokenString, err := GenerateToken(header, claimsMap, secret)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("token: ", tokenString)

	isValid, _ := ValidateToken(tokenString, secret)
	fmt.Println("is token valid: ", isValid)

	duration := time.Second * 4
	time.Sleep(duration)

	isValid, _ = ValidateToken(tokenString, secret)
	fmt.Println("is token valid: ", isValid)

}

What's wrong with implementation above and how to fix it and get rid of warnings?

I decided to use Golang for implementation, however examples in any other languages very appreciated.

答案1

得分: 4

JWT 规范 要求移除所有填充的 = 字符:

> 使用在 RFC 4648 [RFC4648] 第5节中定义的 URL- 和文件名安全字符集进行 Base64 编码,删除所有尾部的 '=' 字符(根据第3.2节的规定),并且不包含任何换行符、空格或其他额外字符。

你可以使用 base64.RawURLEncoding,它创建没有填充的 Base64Url 编码,而不是使用 base64.StdEncoding

你可以在这个简短的 Go Playground 示例 中看到 StdEncodingRawStdEncodingRawURLEncoding 之间的区别。

此外,我强烈建议如果不是为了学习目的,使用一个 JWT 库

英文:

JWT specification requires that all padding = characters are removed:

> Base64 encoding using the URL- and filename-safe character set
defined in Section 5 of RFC 4648 [RFC4648], with all trailing '='
characters omitted (as permitted by Section 3.2) and without the
inclusion of any line breaks, whitespace, or other additional
characters.

You can use base64.RawURLEncoding , which creates Base64Url encoding without padding, instead of base64.StdEncoding.

You can see the differences between the StdEncoding, RawStdEncodingand RawURLEncoding in this short Go Playground example.

Also, I strongly recommend to use a JWT library if it's not for learning exercise.

huangapple
  • 本文由 发表于 2022年4月29日 16:59:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/72055389.html
匿名

发表评论

匿名网友

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

确定