JWT是什么?#
JWT (JSON Web Token)是一个开放标准(RFC 7519),指基于JSON的、用于在网络上声明某种主张的令牌(token),以保证各方之间安全的传输信息。
JWT通过将用户信息加密到token中,服务端不需要保存任何用户信息。服务端只需要通过保存的密钥来验证token正确性,如果正确即通过验证。
JWT的组成#
.
1. 头部(Header)#
algtypJWT
{
"alg": "HS256",
"typ": "JWT"
}
因为JWT是字符串,所以我们还需要对以上内容进行Base64编码,编码后字符串如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2. 载荷(Payload)#
载荷即消息体,这里会存放实际的内容,也就是Token的数据声明(Claim)。这一段有一些是标准字段,当然也可以根据自己需要添加自己需要的字段。标准字段如下:
isssubaudexpnbfiatjti
下面是一个示例:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
同样进行Base64编码后,字符串如下:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
3. 签名(Signature)#
签名是对头部和载荷内容进行签名,一旦前面两部分数据被篡改,只要服务器加密用的密钥没有泄露,得到的签名肯定和之前的签名不一致。
签名的过程:
.
如果用伪代码表示就是(以HS256为例):
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.OFHM3R8PSyHDT_vuzRF5fYkYWdhExM_9pE81kG05qAk
如何使用JWT?#
在之前的传统的方法时在服务端存储一个session,并给客户端返回一个cookie。而如果是使用jwt来做身份鉴定的话,当用户登录成功,会给用户一个token,前端只需要在本地保存该token即可(通常使用localStorage,也可以使用cookie)。
当用户需要访问一个受保护的资源时,需要再Header中使用Bearer模式的Authorization头。其内容看起来是下面这样:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.OFHM3R8PSyHDT_vuzRF5fYkYWdhExM_9pE81kG05qAk
JWT的优缺点#
优点:
- json具有通用性,所以可以跨语言。
- 组成简单,字节占用小,便于传输
- 服务端无需保存会话信息,很容易进行水平扩展
- 一处生成,多处使用,可以在分布式系统中,解决单点登录问题
- 可防护CSRF攻击
缺点:
- payload部分仅仅是进行简单编码,所以只能用于存储逻辑必需的非敏感信息
- 需要保护好加密密钥,一旦泄露后果不堪设想
- 为避免token被劫持,最好使用https协议
- 针对已经办法的令牌,无法作废,不容易应对数据过期的问题。
Golang实现#
github.com/SermoDigital/jose
安装:
go get github.com/SermoDigital/jose
代码示例:
import (
"fmt"
"time"
"github.com/SermoDigital/jose/jws"
)
type Conf struct {
Method string // 加密算法
Key string // 加密key
Issuer string // 签发者
Expire int64 // 签名有效期
}
var conf = Conf{
Method: "HS256",
Key: "sahjdjsgaudsiudhuywge",
Issuer: "testIssuer",
Expire: 100,
}
// GetJWT 获取json web token
func GetJWT(data map[string]interface{}) (token string, err error) {
payload := jws.Claims{}
for k, v := range data {
payload.Set(k, v)
}
now := time.Now()
payload.SetIssuer(conf.Issuer)
payload.SetIssuedAt(now)
payload.SetExpiration(now.Add(time.Duration(conf.Expire) * time.Minute))
jwtObj := jws.NewJWT(payload, jws.GetSigningMethod(conf.Method))
tokenBytes, err := jwtObj.Serialize([]byte(conf.Key))
if err != nil {
return
}
token = string(tokenBytes)
return
}
// VerifyJWT 验证json web token
func VerifyJWT(token string) (ret bool, err error) {
jwtObj, err := jws.ParseJWT([]byte(token))
if err != nil {
return
}
err = jwtObj.Validate([]byte(conf.Key), jws.GetSigningMethod(conf.Method))
if err == nil {
ret = true
}
return
}