JWT(JSON Web Token)是一个很是轻巧的规范。这个规范容许咱们使用JWT在用户和服务器之间传递安全可靠的信息。
一个JWT由3个部分组成:头部(header)、载荷(payload)、签名(signature)。
这三个部分又是由一个分隔符“.” 分割开的。git
用户说明签名的加密算法等,大概以下:github
{
"typ": "JWT",
"alg": "HS256"
}
payload 结构是一个json或者说是map对象
目前有一个相对标准的payload格式web
固然你也能够不用这些字段,能够本身随意定义。redis
签名是由头部和荷载加上一串秘钥,通过头部声明的加密算法加密获得的。由于这个秘钥只有服务端知道,可是这个秘钥一旦泄漏了后果是很严重的。算法
通常使用方法,则是在登陆的时候生成一个token返回到客户端。客户端则能够放到header或者cookie中。每次请求数据的时候带上这个token,而服务端则去验证token是否正确,由于jwt中的秘钥只有服务器知道一旦这个token被别人修改过及时修改过再使用base64编码替换也是能够被发现的,由于签名是把header和payload加起来再么秘钥加密的。以下图:
这样作有几个好处:数据库
可是也带来了一些问题:json
简单的用Gin实现一个http服务端, 一个login接口若是帐号密码正确,则为客户端添加cookie。
第二个接口则是请求数据接口,经过auth中间件来验证cookie中的token是否为以前服务端发出去的那个token,这个只有服务端能验证,由于服务端拥有秘钥。
这个是最简单的实现,没有加上上面说的redis验证。数组
package main import ( "fmt" "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/gomodule/redigo/redis" "time" ) const ( SecretKey = "I have login" ) var redisCoon redis.Conn func main() { router := gin.Default() router.GET("/login", loginHandler) router.Use(authMiddleware) router.GET("/getData", getData) router.Run(":2323") } //验证token中间件 func authMiddleware(ctx *gin.Context) { //从cookie中获取token if tokenStr, err := ctx.Cookie("token"); err == nil { //获取验证以后的结果 token, err := parseToken(tokenStr) if err != nil { ctx.JSON(200, "token verify error") } //若是验证结果是false直接返回token错误我 若是成功则继续下一个handler if token.Valid { ctx.Next() } else { ctx.JSON(200, "token verify error") ctx.Abort() } } else { ctx.JSON(200, "no token") ctx.Abort() } } func getData(ctx *gin.Context) { ctx.JSON(200, "data") } func loginHandler(ctx *gin.Context) { user := ctx.Query("user") pwd := ctx.Query("pwd") if user == "peter" && pwd == "pwd" { token := CreateToken(user, pwd) //ctx.Header("Authorization", token) ctx.SetCookie("token", token, 10, "/", "localhost", false, true) ctx.JSON(200, "ok") } else { ctx.JSON(200, "user is not exit") } } func parseToken(s string) (*jwt.Token, error) { fn := func(token *jwt.Token) (interface{}, error) { return []byte(SecretKey), nil } return jwt.Parse(s, fn) } //建立token func CreateToken(user, pwd string) string { token := jwt.New(jwt.SigningMethodHS256) claims := make(jwt.MapClaims) claims["user"] = user // 这边的pwd 不该该放到claims 荷载中不该该有机密的数据 claims["pwd"] = pwd token.Claims = claims if tokenString, err := token.SignedString([]byte(SecretKey)); err == nil { return tokenString } else { return "" } }
其实源码逻辑挺简单的,就是把上述流程简单的实现。安全
其中的SigningMethod接口是主要签名的方法,在jwt中有几个预置的签名方法。
其实若是咱们本身写一个类而且实现了这个接口,其实也是能够自定义签名方法。服务器
// token结构 type Token struct { Raw string // 保存原始token解析的时候保存 Method SigningMethod // 保存签名方法 目前库里有HMAC RSA ECDSA Header map[string]interface{} // jwt中的头部 Claims Claims // jwt中第二部分荷载,Claims是一个借口 Signature string // jwt中的第三部分 签名 Valid bool // 记录token是否正确 } type Claims interface { Valid() error } // 签名方法 全部的签名方法都会实现这个接口 // 具体能够参考https://github.com/dgrijalva/jwt-go/blob/master/hmac.go type SigningMethod interface { // 验证token的签名,若是有限返回nil Verify(signingString, signature string, key interface{}) error // 签名方法 接受头部和荷载编码事后的字符串和签名秘钥 // 在hmac中key必须是Key must be []byte // 在rsa中key 必须是*rsa.PrivateKey 对象 Sign(signingString string, key interface{}) (string, error) // 返回加密方法的名字 好比'HS256' Alg() string } // 新建token func New(method SigningMethod) *Token { return NewWithClaims(method, MapClaims{}) } func NewWithClaims(method SigningMethod, claims Claims) *Token { // 组成token return &Token{ Header: map[string]interface{}{ "typ": "JWT", "alg": method.Alg(), }, Claims: claims, Method: method, } }
建立签名的逻辑很清晰,下面的注释中已经很清楚了。
// 传入 key 返回token或者error func (t *Token) SignedString(key interface{}) (string, error) { var sig, sstr string var err error // 生成jwt的前两部分string if sstr, err = t.SigningString(); err != nil { return "", err } // 根据不一样的签名method 生成签名字符串 if sig, err = t.Method.Sign(sstr, key); err != nil { return "", err } return strings.Join([]string{sstr, sig}, "."), nil } // 生成jwt的头部和荷载的string func (t *Token) SigningString() (string, error) { var err error parts := make([]string, 2) // 建立一个字符串数组 for i, _ := range parts { var jsonValue []byte if i == 0 { // 把header部分转成[]byte if jsonValue, err = json.Marshal(t.Header); err != nil { return "", err } } else { // 把荷载部分部转成[]byte if jsonValue, err = json.Marshal(t.Claims); err != nil { return "", err } } // 为签名编码 parts[i] = EncodeSegment(jsonValue) } // 用'.'号拼接两部分而后返回 return strings.Join(parts, "."), nil }
有了建立token,就必定有验证token。这个操做通常在服务端的中间件完成。在上面的例子中也能够看到。
// 解析方法的回调函数 方法返回秘钥 能够根据不一样的判断返回不一样的秘钥 type Keyfunc func(*Token) (interface{}, error) func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { return new(Parser).Parse(tokenString, keyFunc) } func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { return new(Parser).ParseWithClaims(tokenString, claims, keyFunc) } func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { // 解析tokenstring 根据'.' 风格以后用base64反编码以后组成 token对象 token, parts, err := p.ParseUnverified(tokenString, claims) if err != nil { return token, err } // 判断parse里的validmethods 是否为空 不为空则循环调用 if p.ValidMethods != nil { var signingMethodValid = false var alg = token.Method.Alg() for _, m := range p.ValidMethods { if m == alg { signingMethodValid = true break } } if !signingMethodValid { // signing method is not in the listed set return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid) } } // 调用keyfunc 返回秘钥 方法从以前的调用注入的方法 var key interface{} if keyFunc == nil { // keyFunc was not provided. short circuiting validation return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable) } if key, err = keyFunc(token); err != nil { // keyFunc returned an error if ve, ok := err.(*ValidationError); ok { return token, ve } return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable} } vErr := &ValidationError{} // 判断是否须要验证claims if !p.SkipClaimsValidation { // valid 方法中会判断 过时时间、签发人、生效时间 若是没有这3个字段则不判断 if err := token.Claims.Valid(); err != nil { if e, ok := err.(*ValidationError); !ok { vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid} } else { vErr = e } } } // 验证jwt中第三部分 签名 调用的是签名方法定义的verify方法 token.Signature = parts[2] if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil { vErr.Inner = err vErr.Errors |= ValidationErrorSignatureInvalid } // 设置valid字段 if vErr.valid() { token.Valid = true return token, nil } return token, vErr }
上面的源码,只是主要的流程。jwt中还有不少代码上面兵没有列出来,好比rsa,ecdsa的具体实现、claims.go里面也有不少逻辑的判断。有兴趣的话能够再深刻研究。