JWT验证 | 青训营笔记
这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天
JWT介绍
相较于传统的session方法,jwt验证无需在服务端储存用户的登录记录,大概流程是这样的:
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
- 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
JWT分为Header,Claims,Signature这三部分。我们只需要管Claims就行,可以自定义我们需要的字段。
JWT验证流程
- 在头部信息中声明加密算法和常量,然后把 header 使用 json 转化为字符串
- 在载荷中声明用户信息,同时还有一些其他的内容,再次使用 json 把在和部分进行转化,转化为字符串
- 使用在 header 中声明的加密算法来进行加密,把第一部分字符串和第二部分的字符串结合和每个项目随机生成的 secret 字符串进行加密,生成新的字符串,此字符串是独一无二的
- 解密的时候,只要客户端带着 jwt 来发起请求,服务端就直接使用 secret 进行解密,解签证解出第一部分和第二部分,然后比对第二部分的信息和客户端穿过来的信息是否一致。如果一致验证成功,否则验证失败。
创建JWT
通常使用 NewWithClaims,因为我们可以通过匿名结构体来实现 Claims 接口,从而携带自己的参数。
源码中的 StandardClaims 结构体:
type StandardClaims struct {
Audience string `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Id string `json:"jti,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
}
关注这些参数:NoteBefore 生效时间、ExpiresAt 过期时间、Issuer 签发者。
Claim可以用结构体写,也可以用map写,这里演示用结构体的写法:
type MyClaim struct {
Username string `json:"username"`
Password string `json:"password"`
jwt.StandardClaims
}
func main() {
//创建一个JWT
mySigningKey := []byte("askldfjaqwiopeklasdjqwerfasdfawerfsldfjkalsdfj") //密钥,不要告诉他人
myclaim := MyClaim{
Username: "金晖", //自定义
Password: "怎么可能告诉你sb", //自定义
StandardClaims: jwt.StandardClaims{
NotBefore: time.Now().Unix() - 60, //生效时间
ExpiresAt: time.Now().Unix() + 2*60*60, //失效时间
Issuer: "金晖", //签发者
},
}
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, myclaim) //创建token(未加密),第一个参数是加密方法,第二个参数是自己定义的结构体
fmt.Println(jwtToken)
s, err := jwtToken.SignedString(mySigningKey) //s就是已签发的token(已加密)
if err != nil {
fmt.Println(err)
}
fmt.Println(s)
}
/*
&{ 0xc000008138 map[alg:HS256 typ:JWT] {金晖 怎么可能告诉你sb { 1674986896 0 金晖 1674979636 }} false}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IumHkeaZliIsInBhc3N3b3JkIjoi5oCO5LmI5Y-v6IO95ZGK6K-J5L2gc2IiLCJleHAiOjE2NzQ5OD
Y4OTYsImlzcyI6IumHkeaZliIsIm5iZiI6MTY3NDk3OTYzNn0.e2SrjjvFSFIgLqmG1VCf3E1FO4Nwd1vjydw-NCDDgRI
*/
解析一个Token
主要通过以下方法实现:
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error)
其中 keyFunc 是个特殊的回调函数,固定接受 *Token 类型指针,返回一个 i 和 err,i 就是我们先前约定的密钥
//假设s是前端返回来的一个用户token,把他解析
token, err := jwt.ParseWithClaims(s, &MyClaim{}, func(token *jwt.Token) (interface{}, error) {
return mySigningKey, nil
})
if err != nil {
fmt.Println(err)
}
获取的 token 是一个 jwtToken 类型的数据,我们需要其中的 Claims。需要对 Claims 进行断言,然后取用即可。
//对Claims进行断言并使用
fmt.Println(token.Claims.(*MyClaim))
fmt.Println(token.Claims.(*MyClaim).Username) //自定义的字段
fmt.Println(token.Claims.(*MyClaim).Password) //自定义的字段