简介
JWT(JSON Web Token),即JSON网络令牌,是一种用于安全传递声明的标准化方式,被广泛应用于身份验证和授权场景,它是一种用于在网络间安全传递声明的一种基于JSON的开放标准。
JWT通常用于在各种网络应用中进行身份验证和授权。服务器将用户的认证信息以JWT的形式生成并返回给客户端,客户端在后续的请求中将该JWT作为身份标识发送至服务器进行验证。由于JWT是使用签名进行验证的,因此服务器可以轻松地验证JWT的真实性,并根据其中的声明信息对用户进行授权。
优缺点
JWT具有以下优点:
- 无需在服务端存储会话信息,因为JWT中已经包含了所有需要的信息。
- 简洁轻量,可以通过网络传输。
- 可以轻松跨平台使用,因为JWT基于开放的标准。
- 可以对JWT进行签名和加密,提高安全性。
JWT具有以下缺点:
- 无法撤销:JWT一旦签发,就无法在有效期内撤销或失效。如果存在安全风险或令牌泄漏,需要等待令牌过期才能解决问题。
- 无法修改:JWT一旦签发,其内容是不可修改的。如果需要修改某些信息,比如增加权限等,需要重新签发新的JWT。
- 数据量较大:由于JWT包含了签名和一些其他信息,相较于传统的session方案,JWT的数据量较大,会增加网络传输的负荷。
- 无法处理会话状态:由于JWT是无状态的,服务器无法在多个请求之间维护会话状态。这在某些应用场景下可能会造成一些问题。
- 安全性依赖密钥管理:JWT的安全性依赖于密钥管理的安全性。如果密钥泄漏,可能导致他人伪造合法的JWT。
- 不支持刷新机制:JWT本身不支持刷新机制,即一旦令牌失效,就需要重新登录获取新的令牌。这对于一些需要长期授权的场景可能不太方便。
结构
JWT由三部分组成,包括头部(Header)、有效载荷(Payload)和签名(Signature)。头部包含描述JWT的元数据,有效载荷包含被称为声明的各种数据,签名是基于头部和有效载荷计算得到的,用于验证JWT的真实性和完整性。
我们来看一个官方网页的例子,更加直观的了解JWT的结构
右侧是JWT的三个组成部分。可以看到头部和载荷是JSON格式,其中包含各种我们需要的数据,而签名则是经过HMACSHA256加密的字符串,这里面包括上面的两个部分外加你自己的secret。当然你可以根据需要配置你想包括和签名的内容,不需要一模一样。
实现
在golang中颁发和验证jwt很容易,有现成的包来供我们使用。
安装
在命令行输入go get github.com/dgrijalva/jwt-go来安装jwt-go。
完成后,将它import到我们的项目中。
颁发JWT
我们先定义一个结构体Claims来定义我们需要在载荷中存储哪些字段。
type Claims struct {
UserId int64
jwt.StandardClaims
}
这里使用了go-jwt包中提供的StandardClaims为我们写好了常用的字段。这里我们就只多使用一个UserId字段,这样鉴权时就能从载荷中读出用户的id。
然后我们看一下颁发JWT的函数
// 颁发token
func ReleaseToken(userId int64) (tokenStr string, err error) {
expirationTime := time.Now().Add(7 * 24 * time.Hour)
claims := &Claims{
UserId: userId,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
IssuedAt: time.Now().Unix(),
Issuer: "richard",
}}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenStr, err = token.SignedString([]byte("my-very-long-256-bit-secret"))
return
}
传入用户的id记录到UserId字段中,获取当前的时间记录到IssuedAt中,加上过期时间(二十四小时)后记录到ExpiresAt中,再将Issuer指定为发行人的标识字符串。之后直接调用封装好的方法NewWithClaims,指定加密算法为jwt.SigningMethodHS256来加密,传入刚刚获得的claims来创建一个token。这个时候还不是最终的JWT字符串,还需要签名才能生成。继续执行token.SignedString,传入你的secret就可以获得到结果了。整体的码量很少,非常简洁美观。你可以将secret存放到例如config这样的包或者环境变量中更加优雅和安全的访问这个核心的密钥。
验证和解析我们的token就更加容易了,下面是实现的代码
// 解析token
func ParseToken(tokenString string) (claims *Claims, ok bool) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("my-very-long-256-bit-secret"), nil
})
if err != nil {
return
}
claims, ok = token.Claims.(*Claims)
return
}
通过ParseWithClaims方法,传入JWT字符串,Claim结构体,和一个名为keyFunc的函数返回secret密钥。当然这里我们只使用了一个密钥,如果你根据不同的头部和载荷信息使用了不同的密钥,可以KeyFunc的参数token中获取到,并且返回对应的密钥。
在Parse完之后如果没有error我们获得到了解析完整的token结构体,可以通过将它的Claims转换成我们定义的Claims,并返回给其他函数使用。
总结
通过了解JWT的结构和实现原理,并且利用jwt-go包实现了简单的JWT颁发和验证,可以在敏感操作中使用JWT来验证操作者的合法性。当然,一般都会作为中间件来使用,减少重复的代码。