【go】JWT安全验证理解与实践

1 JWT

JWT (JSON Web Token)是一个开放标准(RFC 7519),指基于JSON的、用于在网络上声明某种主张的令牌(token),以保证各方之间安全的传输信息。
JWT通过将用户信息加密到token中,服务端不需要保存任何用户信息。服务端只需要通过保存的密钥来验证token正确性,如果正确即通过验证。

2 组成

JWS实际上就是一个字符串,由三部分组成,头部(Header)、载荷(Payload)、签名(Signature),并以.进行拼接。其中头部和载荷都是以JSON格式存放数据,只是进行了编码。

2.1 头部(Header)

每个JWT都会带有头部信息,这里主要声明使用的算法。声明算法的字段名为alg,同时还有一个typ的字段,默认JWT即可。以下示例中算法为HS256。

{
  "alg": "HS256",
  "typ": "JWT"
}

因为JWT是字符串,所以我们还需要对以上内容进行Base64编码,编码后字符串如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2.2 载荷(Payload)

载荷即消息体,这里会存放实际的内容,也就是Token的数据声明(Claim)。这一段有一些是标准字段,当然也可以根据自己需要添加自己需要的字段。标准字段如下:

iss: Token签发者。格式是区分大小写的字符串或者uri,用于唯一标识签发token的一方。
sub: Token的主体,即它的所有人。格式是区分大小写的字符串或者uri。
aud: 接收Token的一方。格式为区分大小写的字符串或uri,或者这两种的数组。
exp: Token的过期时间,格式为时间戳。
nbf: 指定Token在nbf时间之前不能使用,即token开始生效的时间,格式为时间戳。
iat: Token的签发时间,格式为时间戳。
jti: 指此Token的唯一标识符字符串。主要用于实现唯一性保证,防止重放。
下面是一个示例:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

同样进行Base64编码后,字符串如下:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

2.3 签名(Signature)

签名是对头部和载荷内容进行签名,一旦前面两部分数据被篡改,只要服务器加密用的密钥没有泄露,得到的签名肯定和之前的签名不一致。

签名的过程:

对header的json数据进行Base64URL编码,得到一个字符串str1
对payload的json数据进行Base64URL编码,得到一个字符串str2
使用.对以上两个字符串进行拼接,得到字符串str3
使用header中声明的算法,以及服务端的密钥,对拼接字符串进行加密,生成签名
如果用伪代码表示就是(以HS256为例):

HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret
)

将三组字符串,以.相连,就得到了一个完整的token,例如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.OFHM3R8PSyHDT_vuzRF5fYkYWdhExM_9pE81kG05qAk

3 如何使用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实现
在https://jwt.io/为我们推荐了几个golang的Package,我们选用其中的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
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值