JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON方式安全地传输信息。因为此信息是通过数字签名的,所以能够被验证和信任。可使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对对JWT进行签名。php
直白的讲jwt就是一种用户认证(区别于session、cookie)的解决方案。html
众所周知,在jwt出现以前,咱们已经有session、cookie来解决用户登陆等认证问题,为何还要jwt呢?java
这里咱们先了解一下session,cookie。git
熟悉session运行机制的同窗都知道,用户的session数据以file或缓存(redis、memcached)等方式存储在服务器端,客户端浏览器cookie中只保存sessionid。服务器端session属于集中存储,数量不大的状况下,没什么问题,当用户数据逐渐增多到一程度,就会给服务端管理和维护带来大的负担。github
session有两个弊端:golang
一、没法实现跨域。redis
二、因为session数据属于集中管理里,量大的时候服务器性能是个问题。算法
优势:json
一、session存在服务端,数据相对比较安全。跨域
二、session集中管理也有好处,就是用户登陆、注销服务端可控。
cookie也是一种解决网站用户认证的实现方式,用户登陆时,服务器会发送包含登陆凭据的Cookie到用户浏览器客户端,浏览器会将Cookie的key/value保存用户本地(内存或硬盘),用户再访问网站,浏览器会发送cookie信息到服务器端,服务器端接收cookie并解析来维护用户的登陆状态。
cookie避免session集中管理的问题,但也存在弊端:
一、跨域问题。
二、数据存储在浏览器端,数据容易被窃取及被csrf攻击,安全性差。
优势:
一、相对于session简单,不用服务端维护用户认证信息。
二、数据持久性。
jwt经过json传输,php、java、golang等不少语言支持,通用性比较好,不存在跨域问题。传输数据经过数据签名相对比较安全。客户端与服务端经过jwt交互,服务端经过解密token信息,来实现用户认证。不须要服务端集中维护token信息,便于扩展。固然jwt也有其缺点。
缺点:
一、用户没法主动登出,只要token在有效期内就有效。这里能够考虑redis设置同token有效期一直的黑名单解决此问题。
二、token过了有效期,没法续签问题。能够考虑经过判断旧的token何时到期,过时的时候刷新token续签接口产生新token代替旧token。
能够设置有效期,加入有效期是为了增长安全性,即token被黑客截获,也只能攻击较短期。设置有效期就会面临token续签问题,解决方案以下
一般服务端设置两个token
这里推荐个使用比较多的开源项目[github.com/dgrijalva/jwt-go](),更多文档。
示例:
package main import ( "fmt" "github.com/dgrijalva/jwt-go" "time" ) const ( SECRETKEY = "243223ffslsfsldfl412fdsfsdf"//私钥 ) //自定义Claims type CustomClaims struct { UserId int64 jwt.StandardClaims } func main() { //生成token maxAge:=60*60*24 customClaims :=&CustomClaims{ UserId: 11,//用户id StandardClaims: jwt.StandardClaims{ ExpiresAt: time.Now().Add(time.Duration(maxAge)*time.Second).Unix(), // 过时时间,必须设置 Issuer:"jerry", // 非必须,也能够填充用户名, }, } //采用HMAC SHA256加密算法 token:=jwt.NewWithClaims(jwt.SigningMethodHS256, customClaims) tokenString,err:= token.SignedString([]byte(SECRETKEY)) if err!=nil { fmt.Println(err) } fmt.Printf("token: %v\n", tokenString) //解析token ret,err :=ParseToken(tokenString) if err!=nil { fmt.Println(err) } fmt.Printf("userinfo: %v\n", ret) } //解析token func ParseToken(tokenString string)(*CustomClaims,error) { token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return []byte(SECRETKEY), nil }) if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { return claims,nil } else { return nil,err } }
运行结果:
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjExLCJleHAiOjE1OTA5MTk1NDAsImlzcyI6ImplcnJ5In0.FppmbbHRrS4wd5wen73vYPOvtzycOrn2JZlK6JRjEGk
userinfo: &{11 { 1590919540 0 jerry 0 }}
以上用到了CustomClaims,也能够用简单的方法
示例
package main import ( "fmt" "github.com/dgrijalva/jwt-go" "time" ) const ( SECRETKEY = "243223ffslsfsldfl412fdsfsdf"//私钥 ) //自定义Claims type CustomClaims struct { UserId int64 jwt.StandardClaims } func main() { //生成token maxAge:=60*60*24 // Create the Claims //claims := &jwt.StandardClaims{ // // ExpiresAt: time.Now().Add(time.Duration(maxAge)*time.Second).Unix(), // 过时时间,必须设置, // // Issuer: "jerry",// 非必须,也能够填充用户名, // //} //或者用下面自定义claim claims := jwt.MapClaims{ "id": 11, "name": "jerry", "exp": time.Now().Add(time.Duration(maxAge)*time.Second).Unix(), // 过时时间,必须设置, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString([]byte(SECRETKEY)) if err!=nil { fmt.Println(err) } fmt.Printf("token: %v\n", tokenString) //解析token ret,err :=ParseToken(tokenString) if err!=nil { fmt.Println(err) } fmt.Printf("userinfo: %v\n", ret) } //解析token func ParseToken(tokenString string)(jwt.MapClaims,error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { // Don't forget to validate the alg is what you expect: if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") return []byte(SECRETKEY), nil }) if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { return claims,nil } else { return nil,err } }
运行结果相似
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTA5MzUzMDUsImlkIjoxMSwibmFtZSI6ImplcnJ5In0.fapE0IiOEe_TqoMCThbNTHUvgWiHPEk0rm-9uPIcvPU
userinfo: map[exp:1.590935305e+09 id:11 name:jerry]
HttpOnly
的标记,能够防止 XSS) 攻击。参考资料
https://github.com/guyan0319/...
https://godoc.org/github.com/...
https://blog.csdn.net/weixin_...
https://www.cnblogs.com/flipp...