整合JWT
What is JWT
- JWT 是 JSON Web Token 的缩写,是为了在⽹络应⽤环境间传递声明⽽执⾏的⼀种基于JSON的开放标准((RFC 7519)。JWT 本身没有定义任何技术实现,它只是定义了⼀种基于 Token 的会话管理的规则,涵盖 Token 需要包含的标准内容和 Token 的⽣成过程,特别适⽤于分布式站点的单点登录(SSO)场景
- 一段JWT Token 由”.”切割成头部、负载、签名三部分
JWT优缺点
- JWT 拥有基于 Token 的会话管理⽅式所拥有的⼀切优势,不依赖 Cookie,使得其可以防⽌ CSRF 攻击,也能在禁⽤ Cookie 的浏览器环境中正常运⾏。
- ⽽ JWT 的最⼤优势是服务端不再需要存储 Session,使得服务端认证鉴权业务可以⽅便扩展,避免存储Session 所需要引⼊的 Redis 等组件,降低了系统架构复杂度。但这也是 JWT 最⼤的劣势,由于有效期存储在 Token 中,JWT Token ⼀旦签发,就会在有效期内⼀直可⽤,⽆法在服务端废⽌,当⽤户进⾏登出操作,只能依赖客户端删除掉本地存储的 JWT Token,如果需要禁⽤⽤户,单纯使⽤ JWT 就⽆法做到了
Get JWT-Go
go get -u github.com/dgrijalva/jwt-go@v3.2.0
Where JWT-Token
- 客户端携带Token可以有三个位置:1.请求头 2.请求体 3.放在URI
- Token放在Headher的Authorization中,并且使用Bearer开头
Prepare Middleware-JWT
Prepare jwt auth(Middleware)
package jwt
import ( "errors" "github.com/dgrijalva/jwt-go" "time" )
const TokenExpireTime = time.Hour * 2
var mySecret = []byte("Hello,Piwriw")
type MyClaims struct { UserID int64 `json:"user_id"` UserName string `json:"username"` jwt.StandardClaims }
func GenToken(userID int64, username string) (string, error) { c := MyClaims{ userID, "username", jwt.StandardClaims{ ExpiresAt: time.Now().Add(TokenExpireTime).Unix(), Issuer: "my-project", }, } token := jwt.NewWithClaims(jwt.SigningMethodES256, c) return token.SignedString(mySecret)
}
func ParseToken(tokenString string) (*MyClaims, error) { token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(*MyClaims); ok && token.Valid { return claims, nil } return nil, errors.New("invalid token") }
|
Prepare jwt request
package controller
import ( "errors" "github.com/gin-gonic/gin" ) const CtxUserIDKey="userID" var ErrorNotLogin=errors.New("用户未登录")
func getCurrentUser(c *gin.Context)(userID int64,err error) { uid,ok:=c.Get(CtxUserIDKey) if !ok { err=ErrorNotLogin return } userID,ok=uid.(int64) if!ok{ err=ErrorNotLogin return } return }
|
How to use JWT
Gin 中注册中间件模式
v1.Use(middlewares.JWTAuthMiddleware()) // 应用JWT认证中间件
Example - 在Login中添加Code
func Login(p *models.ParamLogin)(token string,err error) { user := &models.User{ UserName: p.Username, Password: p.Password, } if err:=mysql.Login(user);err!=nil{ return "",err } return jwt.GenToken(user.UserID,user.UserName) }
|
JWT - Refresh token
前⾯讲的 Token,都是 Access Token,也就是访问资源接⼝时所需要的 Token,还有另外⼀种Token,Refresh Token,通常情况下,Refresh Token 的有效期会⽐较⻓,⽽ Access Token 的有效期
⽐较短,当 Access Token 由于过期⽽失效时,使⽤ Refresh Token 就可以获取到新的 Access Token,如果 Refresh Token 也失效了,⽤户就只能重新登录了。
- 在 JWT 的实践中,引⼊ Refresh Token,将会话管理流程改进如下。
客户端使⽤⽤户名密码进⾏认证
- 服务端⽣成有效时间较短的 Access Token(例如 10 分钟),和有效时间较⻓的 RefreshToken(例如 7 天)
- 客户端访问需要认证的接⼝时,携带 Access Token
- 如果 Access Token 没有过期,服务端鉴权后返回给客户端需要的数据
如果携带 Access Token 访问需要认证的接⼝时鉴权失败(例如返回 401 错误),则客户端使⽤Refresh Token 向刷新接⼝申请新的 Access Token
- 如果 Refresh Token 没有过期,服务端向客户端下发新的 Access Token
- 客户端使⽤新的 Access Token 访问需要认证的接⼝
GenTokenWithRefreshToken 生成RefreshToken
func GenTokenWithRefreshToken(userID int64) (acToken, reToken string, err error) { c := MyClaims{ userID, "username", jwt.StandardClaims{ ExpiresAt: time.Now().Add(TokenExpireTime).Unix(), Issuer: "Piwirw", }, } acToken, err = jwt.NewWithClaims(jwt.SigningMethodES256, c).SignedString(mySecret)
reToken, err = jwt.NewWithClaims(jwt.SigningMethodES256, jwt.StandardClaims{ ExpiresAt: time.Now().Add(time.Second * 30).Unix(), Issuer: "Piwriw", }).SignedString(mySecret) return }
func ParseToken2(tokenString string)(claims *MyClaims,err error) { var token *jwt.Token claims=new(MyClaims) token,err=jwt.ParseWithClaims(tokenString,claims, func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err != nil { return } if !token.Valid { err=errors.New("Invalid token") } return }
|
RefreshToken 刷新token
func RefreshToken(acToken, reToken string) (newToken, newReToken string, err error) { _,err=jwt.Parse(reToken,func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err!=nil{ return } var claims MyClaims _,err=jwt.ParseWithClaims(acToken,&claims,func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) v,_:=err.(*jwt.ValidationError) if v.Errors==jwt.ValidationErrorExpired{ return GenTokenWithRefreshToken(claims.UserID) } return }
|
ParseToken 解析Access Token
func ParseToken2(tokenString string)(claims *MyClaims,err error) { var token *jwt.Token claims=new(MyClaims) token,err=jwt.ParseWithClaims(tokenString,claims, func(token *jwt.Token) (i interface{}, err error) { return mySecret, nil }) if err != nil { return } if !token.Valid { err=errors.New("Invalid token") } return }
|