JWT(json web token)
jwt
jwt的原理和session有點相像,其目的是為了解決rest api中無狀態性
因為rest接口,需要權限校驗。但是又不能每個請求都把用戶名密碼傳入,因此產生了這個token的方法
流程:
https://blog.wangjunfeng.com/post/golang-jwt/#3-%E7%AD%BE%E5%90%8D-signature
-
用戶訪問auth接口,獲取token
服務器校驗用戶傳入的用戶名密碼等信息,確認無誤后,產生一個token。
這個token其實是類似於map的數據結構(jwt數據結構)中的key。准確的應該是:token中其實就保存了用戶的信息,只是被加密過了。怪不得服務器重啟了token還能使用,就是這個原因,因為數據就是保存在token這條長長的字符串中的。
-
用戶訪問需要權限驗證的接口,並傳入token。
服務器驗證token:根據自己的token密鑰判斷token是否正確(是否被別人篡改),正確后才從token中解析出token中的信息。可能會把解析出的信息保存在context中
使用步驟
-
下載依賴包
go get -u github.com/dgrijalva/jwt-go
-
編寫jwt工具包,用戶創建和檢查token
分為幾個部分:
- 指定加密密鑰
- 指定被保存在token中的實體對象,Claims 結構體。需要內嵌jwt.StandardClaims。這個結構體是用來保存信息的。
- 根據數據產生token:根據傳入的信息,組裝成一個Claims結構體對象,再從對象中獲取token
- 根據token解析數據:解析出token所對應的interface{},再使用斷言解析出Claims對象,取數據
/ 指定加密密鑰 var jwtSecret=[]byte(setting.JwtSecret) //Claim是一些實體(通常指的用戶)的狀態和額外的元數據 type Claims struct{ Username string `json:"username"` Password string `json:"password"` jwt.StandardClaims } // 根據用戶的用戶名和密碼產生token func GenerateToken(username ,password string)(string,error){ //設置token有效時間 nowTime:=time.Now() expireTime:=nowTime.Add(3*time.Hour) claims:=Claims{ Username: username, Password: password, StandardClaims: jwt.StandardClaims{ // 過期時間 ExpiresAt:expireTime.Unix(), // 指定token發行人 Issuer:"gin-blog", }, } tokenClaims:=jwt.NewWithClaims(jwt.SigningMethodHS256,claims) //該方法內部生成簽名字符串,再用於獲取完整、已簽名的token token,err:=tokenClaims.SignedString(jwtSecret) return token,err } // 根據傳入的token值獲取到Claims對象信息,(進而獲取其中的用戶名和密碼) func ParseToken(token string)(*Claims,error){ //用於解析鑒權的聲明,方法內部主要是具體的解碼和校驗的過程,最終返回*Token tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { return jwtSecret, nil }) if tokenClaims!=nil{ // 從tokenClaims中獲取到Claims對象,並使用斷言,將該對象轉換為我們自己定義的Claims // 要傳入指針,項目中結構體都是用指針傳遞,節省空間。 if claims,ok:=tokenClaims.Claims.(*Claims);ok&&tokenClaims.Valid{ return claims,nil } } return nil,err }
-
編寫路由,返回token
(需要做用戶參數校驗與錯誤處理)
- 用戶參數校驗
type auth struct{ Username string `valid:"Required;MaxSize(50)"` Password string `valid:"Required;MaxSize(50)"` } func GetAuth(c *gin.Context){ username:=c.Query("username") password:=c.Query("password") valid:=validation.Validation{} a:=auth{ Username: username, Password: password, } // 與之前的對每一個數據分開驗證不同,此處在auth對象中通過定義標簽valid // 一次性校驗對象中的所有字段信息 ok,_:=valid.Valid(&a) //創建返回信息 data:=make(map[string]interface{}) code:=e.INVALID_PARAMS /* 根據用戶名密碼獲取token 判斷流程: 1. 先判斷用戶名密碼是否存在 */ if ok{ isExist:=models.CheckAuth(username,password) if isExist{ token,err:=util.GenerateToken(username,password) if err!=nil{ code=e.ERROR_AUTH_TOKEN }else{ data["token"]=token code=e.SUCCESS } }else{ code=e.ERROR_AUTH } }else{ //如果數據驗證失敗,則打印結果 for _,err:=range valid.Errors{ log.Println(err.Key,err.Message) } } c.JSON(http.StatusOK,util.ReturnData(code,e.GetMsg(code),data)) }
-
編寫中間件,校驗token字符串
func JWY()gin.HandlerFunc{ return func(c *gin.Context){ var code int var data interface{} code=e.SUCCESS token:=c.Query("token") if token==""{ code=e.ERROR_AUTH_NO_TOKRN }else{ claims,err:=util.ParseToken(token) if err!=nil{ code=e.ERROR_AUTH_CHECK_TOKEN_FAIL }else if time.Now().Unix()>claims.ExpiresAt{ code=e.ERROR_AUTH_CHECK_TOKEN_TIMEOUT } } //如果token驗證不通過,直接終止程序,c.Abort() if code!=e.SUCCESS{ // 返回錯誤信息 c.JSON(http.StatusUnauthorized,util.ReturnData(code,e.GetMsg(code),data)) //終止程序 c.Abort() return } c.Next() } }