Json web token (JWT)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q
三部分构成
- 第一部头部(header),作用:
- 声明类型
- 声明加密的算法 通常直接使用 HMAC SHA256
{'typ': 'JWT','alg': 'HS256'}
- 将头部进行base64加密
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
- 第二部分载荷(payload),载荷就是存放有效信息的地方:
- 标准中注册的声明
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
- 公共的声明
- 公共的声明可以添加任何的信息
- 一般添加用户的相关信息或其他业务需要的必要信息
- 议添加敏感信息,因为该部分在客户端可解密
- 私有的声明
- 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息
- 举例
{"sub": "1234567890","name": "John Doe","admin": true}然后将其进行base64加密,得到Jwt的第二部分 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9signature
- 标准中注册的声明
- 第三部分签证(signature)
-
header (base64后的)
-
payload (base64后的)
-
secret
-
需要base64加密后的header和base64加密后的payload使用"."连接组成的字符串(头部在前),
-
然后通过header中声明的加密方式进行加secret组合加密,
-
构成了jwt的第三部分
-
举例
UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q
- secret 需要保存在服务端用来生成token和验证
-
签名的目的
- 对头部以及载荷内容进行签名
- 加密算法对于不同的输入产生的输出总是不一样
- 对于两个不同的输入,产生同样的输出的概率极其地小
- JWT的头部中已经用alg字段指明加密算法
- 服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,说明这个Token的内容被别人动过的,我们应该拒绝这个Token,返回一个HTTP 401 Unauthorized响应
- 在JWT中,不应该在载荷里面加入任何敏感的数据,比如用户的密码
如何应用
- 一般是在请求头里加入Authorization,并加上Bearer标注
fetch('api/user/1', { headers: {'Authorization': 'Bearer ' + token }})服务端会验证token 如果验证通过,就会返回相应的资源
Golang 客户端实现
-
调用包 https://github.com/tutengdihuang/jwt
-
单例模式
var jwtIns *JwtAuth
type JwtAuth struct {
Lock *sync.Mutex
algorithm *jwt.Algorithm
claimsMap map[string]*jwt.Claims //key: userID
}
func GetJwtAuth() *JwtAuth {
if jwtIns == nil {
once := sync.Once{}
once.Do(
func() {
if jwtIns == nil {
jwtIns = new(JwtAuth)
jwtIns.Lock = new(sync.Mutex)
jwtIns.claimsMap = make(map[string]*jwt.Claims)
secret := config_center.GetViperConf().Sub("jwt").GetString("secret")
algorithm := jwt.HmacSha256(secret)
jwtIns.algorithm = &algorithm
}
})
}
return jwtIns
}
- get token
var jwtClaimUserKey = "username"
var jwtClaimUserIdKey = "id"
func (this *JwtAuth) GetToken(userName string, id int) (token string, err error) {
claim := jwt.NewClaim()
claim.Set(jwtClaimUserKey, userName)
claim.Set(jwtClaimUserIdKey, strconv.Itoa(id))
//claim.SetTime("expire", time.Now().Add(30*time.Minute))
idstr := strconv.Itoa(id)
this.Lock.Lock()
defer this.Lock.Unlock()
this.claimsMap[idstr] = claim
token, err = this.algorithm.Encode(claim)
return
}
- validate check
func (this *JwtAuth) Validate(idstr, token string) bool {
this.Lock.Lock()
defer this.Lock.Unlock()
if _, ok := this.claimsMap[idstr]; !ok {
return false
}
if this.algorithm.Validate(token) != nil {
return false
}
return true
}
- token decode
func (this *JwtAuth) TokenDecode(token string) (string, int, error) {
claim, err := this.algorithm.Decode(token)
if err != nil {
return "", 0, err
}
userName, err := claim.Get(jwtClaimUserKey)
if err != nil {
return "", 0, err
}
idStr, err := claim.Get(jwtClaimUserIdKey)
if err != nil {
return "", 0, err
}
id, _ := strconv.Atoi(idStr)
return userName, id, nil
}
- token remove
func (this *JwtAuth) TokenRemove(id string) {
this.Lock.Lock()
defer this.Lock.Unlock()
delete(this.claimsMap, id)
}