golang-jwt/jwt使用手册

安装

当前最新版本为v4版本,新增了 Go 模块支持,但依旧保留了向后兼容性

1
go get -u github.com/golang-jwt/jwt/v4

官方规定的一些声明Claims(载荷)

这些声明都是可选的,不强制的。只是为后续操作提供方便,可操作性的起始点数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type RegisteredClaims struct {
// the `iss` (Issuer) claim. 发行人:声明JWT的发行主体,一般固定为应用名称
Issuer string `json:"iss,omitempty"`

// the `sub` (Subject) claim. 主题:声明JWT的发行主题,主题值必须全局惟一的
Subject string `json:"sub,omitempty"`

// the `aud` (Audience) claim. 接收者:具体的客户端应用
Audience ClaimStrings `json:"aud,omitempty"`

// the `exp` (Expiration Time) claim. 到期时间
ExpiresAt *NumericDate `json:"exp,omitempty"`

// the `nbf` (Not Before) claim. 可用开始时间
NotBefore *NumericDate `json:"nbf,omitempty"`

// the `iat` (Issued At) claim. 颁发时间
IssuedAt *NumericDate `json:"iat,omitempty"`

// the `jti` (JWT ID) claim. JWT的唯一标识符,注意:和sub是不一样的概念
ID string `json:"jti,omitempty"`
}

对称加密

(例如 HSA)仅使用一个密钥。这可能是最简单的签名方法,因为任何[]byte都可以用作有效的秘密。它们在计算上的使用速度也略快一些,尽管这很少有关系。当令牌的生产者和消费者都受信任,甚至是同一个系统时,对称签名方法效果最好。由于相同的密钥用于签名和验证令牌,因此您无法轻松分发密钥以进行验证。

代码实现

生成token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const signKey = "this is your sign key"

// CustomClaims 自定义jwt的claims信息,包含官方注册的claims
type CustomClaims struct {
jwt.RegisteredClaims
Email string `json:"email"`
}

// CreateToken Create a jwt token from request.
func (*helloApi) CreateToken(r *ghttp.Request) {
nowTime := time.Now()
userId := "123456" // 用户ID(唯一标识)
appName := "coolcar" // 应用名称
expireIn := 7 * 24 * time.Hour // 有效时长
var token = jwt.NewWithClaims(jwt.SigningMethodHS256, CustomClaims{
RegisteredClaims: jwt.RegisteredClaims{
Issuer: appName,
Subject: userId,
ExpiresAt: jwt.NewNumericDate(nowTime.Add(expireIn)),
NotBefore: jwt.NewNumericDate(nowTime),
IssuedAt: jwt.NewNumericDate(nowTime),
},
Email: "595929049@qq.com", // 自定义的claims
})

tokenString, _ := token.SignedString([]byte(signKey))

_ = r.Response.WriteJson(g.Map{
"token": tokenString,
"expire_in": int(expireIn.Seconds()),
})
}

使用token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func (*helloApi) ParseToken(r *ghttp.Request) {
tokenString := r.GetHeader("authorization")
// 注意区分strings.TrimPrefix 和 strings.TrimLeft的区别
tokenString = strings.TrimPrefix(tokenString, "Bearer ")

// 加入claims的验证
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(signKey), nil
})

claims, ok := token.Claims.(*CustomClaims)
if !ok || !token.Valid {
if errors.Is(err, jwt.ErrTokenMalformed) {
r.Response.WriteExit("That's not even a token")
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
// Token is either expired or not active yet
r.Response.WriteExit("Timing is everything")
} else {
r.Response.WriteExit("Couldn't handle this token:", err)
}
}

_ = r.Response.WriteJson(g.Map{
"email": claims.Email,
"user_id": claims.Subject,
"app_name": claims.Issuer,
})
}

非对称加密

(例如 RSA)使用不同的密钥来签名和验证令牌。这使得使用私钥生成令牌成为可能,并允许任何消费者访问公钥进行验证。

代码实现

生成token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const privateKey = `-----BEGIN PRIVATE KEY-----
MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAs57i3yugK3vbC6G2
UVTw8Vk7BMARaSN1W6ouF59X2N/8ocMbchNxofEudf/2X6Us2V1srFgvPC/CE1N2
ncDMXwIDAQABAkAT6VwbAzoJN/yrSGzujSz8hDi/qQ8FCbI7zBy576cMWnCKFwTT
KDWfxaW9sISqsiHFoBiq0EHj99H0/x6o014pAiEA4g7yxcu2m6yOQSlaWsA9vHnd
vuhrPcgXWQfQTqDGodUCIQDLaVrbH4H71uV/svegy4dG8T7U4c8/mil3x9qOowPb
YwIhALn7DmUIyn2dI5Qcj4emLaSIppTP5pr3qa3HretifsjZAiBHkdsw7CYdCSCY
zMyKG/KOCIX1+zmjhEeA6KXCuCK8RwIgC2synVWxCtKMJdj782Ksw5+rh8fM56Si
tmgaHfusqJw=
-----END PRIVATE KEY-----`

// CustomClaims 自定义jwt的claims信息,包含官方注册的claims
type CustomClaims struct {
jwt.RegisteredClaims
Email string `json:"email"` // 自定义的claims
}

// CreateToken Create a jwt token from request.
func (*helloApi) CreateToken(r *ghttp.Request) {
nowTime := time.Now()
userId := "123456" // 用户ID(唯一标识)
appName := "coolcar" // 应用名称
expireIn := 7 * 24 * time.Hour // 有效时长
var token = jwt.NewWithClaims(jwt.SigningMethodRS256, CustomClaims{
RegisteredClaims: jwt.RegisteredClaims{
Issuer: appName,
Subject: userId,
ExpiresAt: jwt.NewNumericDate(nowTime.Add(expireIn)),
NotBefore: jwt.NewNumericDate(nowTime),
IssuedAt: jwt.NewNumericDate(nowTime),
},
Email: "595929049@qq.com", // 自定义的claims
})
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateKey))
if err != nil {
log.Fatal("parse rsa private key error :", err)
}
tokenString, _ := token.SignedString(privateKey)

_ = r.Response.WriteJson(g.Map{
"token": tokenString,
"expire_in": int(expireIn.Seconds()),
})
}
结果
1
2
3
4
{
"expire_in": 604800,
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjb29sY2FyIiwic3ViIjoiMTIzNDU2IiwiZXhwIjoxNjYwODA2OTYzLCJuYmYiOjE2NjAyMDIxNjMsImlhdCI6MTY2MDIwMjE2MywiZW1haWwiOiI1OTU5MjkwNDlAcXEuY29tIn0.cr4KPReapVGyiFjmNqGPEIIUrFLSO8PgpoWVFmMnWEzFzd9RBpwJdSLywcE5k7nrwpR9TexPk-hRnxgBta3t5w"
}

使用token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const publicKey = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALOe4t8roCt72wuhtlFU8PFZOwTAEWkj
dVuqLhefV9jf/KHDG3ITcaHxLnX/9l+lLNldbKxYLzwvwhNTdp3AzF8CAwEAAQ==
-----END PUBLIC KEY-----`

func (*helloApi) ParseToken(r *ghttp.Request) {
tokenString := r.GetHeader("authorization")
// 注意区分strings.TrimPrefix 和 strings.TrimLeft的区别
tokenString = strings.TrimPrefix(tokenString, "Bearer ")

//token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// return jwt.ParseRSAPublicKeyFromPEM([]byte(publicKey))
//})

// 加入claims的验证
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwt.ParseRSAPublicKeyFromPEM([]byte(publicKey))
})

claims, ok := token.Claims.(*CustomClaims)
if !ok || !token.Valid {
if errors.Is(err, jwt.ErrTokenMalformed) {
r.Response.WriteExit("That's not even a token")
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
// Token is either expired or not active yet
r.Response.WriteExit("Timing is everything")
} else {
r.Response.WriteExit("Couldn't handle this token:", err)
}
}

_ = r.Response.WriteJson(g.Map{
"email": claims.Email,
"user_id": claims.Subject,
"app_name": claims.Issuer,
})
}
结果
1
2
3
4
5
{
"app_name": "coolcar",
"email": "595929049@qq.com",
"user_id": "123456"
}