JWT 全 chen JSON Web Tokens 现在被广泛的应用于各种前后端分离的场景,他比传统的 Token Session 方式,更具灵活性。
当然网上也有很多开源的 JWT 库,非常之多,开源组织也提供了官方的库。
可以访问这网址去下载:
https://jwt.io
复制代码
但是如果我们知其然而不知其所以然的使用,难免会有很多问题。
所以这次分享下,我最近自己写的一个 JWT 库,代码已经上传到 github 上了,地址如下:
https://github.com/liu578101804/go-tool/tree/master/jwt
复制代码
相比出名的 JWT 库来说,我没有任何优势,只是作为学习使用,欢迎大家指正其中的不足。
下面就给大家说下我的实现思路吧。
JWT 的原理
我们要实现 JWT 算法就得先了解他的原理,我尽量用剪短的话去解释:
JWT 算法输出的数据是一串包含 header(头信息).payload(内容).signature(签名)
的一段字符串。
来一段真实的 JWT 生成的字符串:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
复制代码
是不是豁然开朗了,这串字符串又叫 token ,因为这串 token 里面包含了验证需要的信息,相比传统的 session 需要到服务器里面去取验证的信息,更加的灵活独立。
因为他不依赖 session 这种传统的存储,别用在分布式服务器里面有着很大的优势。
当然他也有缺点,最头痛就这 2 点:
-
不可控。 一旦签发出去的 token 将无法提前让他销毁,不像传统的,我可以把我 session 里面的 token 删了下次过来,token 就失效了。当然这也不是没办法解决,在整个体系里面加入黑名单机制就行,只是稍微麻烦了点。
-
信息相对传统的 session 数据量和隐私性没那么好。 因为 token 一般都不建议特别长,所以 payload 承载的数据量是有限的。同时字符串里面的 payload 是可以被解密的,所以存在一定性的被破译风险(当然你可以使用比较难破译的算法去降低这个风险)。
算法组成
JWT 的算法组成很简单,只需要 一个可逆的加密算法去加密 header(头信息).payload(内容)
,一个不可逆的算法去对前面这部分内容进行加密签名生成 signature(签名)
就行。
如果我们用不同的加密算法组合便形成了不同的 JWT 加密算法。比如:
- HS256 (HMAC + SHA-256)
- RS256 (RSA + SHA-256)
当然还有很多,你可以自己去组合,我们将写的这个库支持你自定义。
具体实现
下面就开始进入代码实现阶段了:
说下我的设计思路,Golang 他有一个天然的优势就是支持把函数作为变量传入,我们便可以根据这一特性把加密部分让调用者去实现,我们把实现主体就行,这样便我们的 JWT 便非常灵活了。
我们要写的主体代码去掉注释空行不到 100 行。
jwt.go
package jwt
import (
"encoding/json"
"strings"
"fmt"
)
//声明一个标准的JWT接口
type IJwt interface {
//设置头部
SetHeader(string)
//设置签名算法
SetSignFunc(SignFunc)
//设置编码算法
SetEncodeFunc(EncodeFunc)
//写入body
WriteBody(map[string]interface{})
//生成jwt
CreateJwtString() (string,error)
//验证jwt
CheckJwtString(string) bool
}
//规范header的格式
type Header struct {
Type string `json:"type"`
Alg string `json:"alg"`
}
//签名算法
type SignFunc func([]byte) string
//编码算法
type EncodeFunc func([]byte) string
//声明一个结构图 去实现 标准的JWT接口
type Jwt struct {
Header Header
Body map[string]interface{}
signFun SignFunc
encodeFun EncodeFunc
}
//设置头部信息,说明你使用的签名算法
func (j *Jwt) SetHeader(headerType string){
j.Header = Header{
Type: "JWT",
Alg: headerType,
}
}
//设置签名算法
func (j *Jwt) SetSignFunc(signFunc SignFunc) {
j.signFun = signFunc
}
//设置对 header 和 body 的加密算法
func (j *Jwt) SetEncodeFunc(encodeFunc EncodeFunc) {
j.encodeFun = encodeFunc
}
//写入要加密的内容
func (j *Jwt) WriteBody(body map[string]interface{}) {
j.Body = body
}
//生成token
func (j *Jwt) CreateJwtString() (string,error) {
//编码header
headerByte,err := json.Marshal(j.Header)
if err != nil {
return "",err
}
headerStr := j.encodeFun(headerByte)
//编码body
bodyByte,err := json.Marshal(j.Body)
if err != nil {
return "",err
}
bodyStr := j.encodeFun(bodyByte)
//签名
signByte := j.signFun([]byte(string(headerStr)+"."+string(bodyStr)))
return fmt.Sprintf("%s.%s.%s",headerStr,bodyStr,signByte),nil
}
//验证 token 是否合规
func (j *Jwt) CheckJwtString(input string) bool {
arr := strings.Split(input,".")
//格式是否正确
if len(arr) != 3 {
return false
}
//签名
signByte := j.signFun([]byte(string(arr[0])+"."+string(arr[1])))
if string(signByte) != arr[2] {
return false
}
return true
}
复制代码
这个文件就已经把 JWT 的核心给写好了,现在只需要根据你想要的加密算法进行填充就好了。
下面我们就去实现一个最简单的 RS256
算法,新建一个 bs.go
文件,内容如下:
package jwt
import (
"crypto/sha256"
"encoding/base64"
"fmt"
)
func NewRS256() IJwt {
jwtM := Jwt{}
//Sha256
jwtM.SetSignFunc(func(bytes []byte) string {
h := sha256.New()
h.Write(bytes)
return fmt.Sprintf("%x",h.Sum(nil))
})
//base64
jwtM.SetEncodeFunc(func(bytes []byte) string {
return base64.URLEncoding.EncodeToString(bytes)
})
return &jwtM
}
复制代码
我这里 header 和 payload 采用 base64 去加密,签名采用 sha256
当然这种算法生成的 JWT 很容易被人串改模仿,不能用于生产的。
到这里我们的分享也就结束了,赶快去实现你的 JWT 算法吧,有任何疑问欢迎留言。
有疑问加站长微信联系(非本文作者)