英文:
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:
-
Warning: Looks like your JWT signature is not encoded correctly using base64url (https://tools.ietforg/html/rfc4648#section-5). Note that padding ("=") must be omitted as per https://tools.ietf.org/html/rfc7515#section-2
-
Warning: Looks like your JWT header is not encoded correctly using base64url (https://tools.ietforg/html/rfc4648#section-5). Note that padding ("=") must be omitted as per https://tools.ietf.org/html/rfc7515#section-2
-
Warning: Looks like your JWT payload is not encoded correctly using base64url (https://tools.ietforg/html/rfc4648#section-5). Note that padding ("=") must be omitted as per https://tools.ietf.org/html/rfc7515#section-2
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 示例 中看到 StdEncoding
、RawStdEncoding
和 RawURLEncoding
之间的区别。
此外,我强烈建议如果不是为了学习目的,使用一个 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
, RawStdEncoding
and RawURLEncoding
in this short Go Playground example.
Also, I strongly recommend to use a JWT library if it's not for learning exercise.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论