本文核心內容是利用jwt-go中間件來開發golang webapi用戶登錄模塊的token下發和驗證,小程序登錄功能只是一個切入點,這套邏輯一樣適用於其餘客戶端的登錄處理。git
小程序的登錄邏輯在其餘博主的文章中已經總結得很是詳盡,好比我參考的是這篇博文:微信小程序登陸邏輯整理,因此在這裏再也不贅述,只是大體概括一下個人實現流程:github
在小程序端調用wx.login
方法,異步得到到微信下發的 jscode ,而後將 jscode 發送到 golang 服務端(若是須要詳細用戶信息,見參考博文的實現邏輯,流程大體類似);golang
將 uid 做爲關鍵信息,生成 jwt 格式的 token 字符串,返回給小程序客戶端,小程序收到 token 斷定登錄成功,並將 token 存入 localstorage ,之後的每次請求先讀取 localstorage 中的 token 放入請求頭部做爲身份標識,若是 token 失效或者沒法讀取,則從新執行登錄流程。web
因爲小程序段邏輯簡單,並且不是本文討論重點,代碼實現就不貼出了,後面應該也不會再補。數據庫
服務端實現是本文的重頭戲,因爲 golang 語言的特性,我在實際開發中也踩了一些不大不小的坑,在這裏進行詳細記錄,也做爲一個經驗總結,同時加深印象。服務端流程能夠大體分爲如下幾個大步驟:json
根據客戶端發送的 jscode 得到用戶 open_id小程序
利用 open_id 獲取平臺 uid ,同時使用 jwt-go 中間件實現 token 的生成微信小程序
封裝 token 驗證中間件,判斷請求是否合法api
本文的代碼實現是在gin
框架基礎上完成的,gin
是一個很是輕量的 web http 處理框架,很符合 golang 輕框架的理念,但高度靈活也要求了必定的自主開發能力,好比請求數據庫讀寫、請求信息讀取和一些中間件的使用,這些都須要本身查找不一樣包的官方文檔,去檢索Api和查找對應的解決方案。雖然如此,但做爲習慣了 .net平臺 高度封裝和甜到發膩的語法糖的 .neter ,在 golang 開發過程當中也體會到了不同的樂趣。廢話很少說,若是對 golang 開發感興趣的話,gin
是一個我十分推薦的上手框架。服務器
在這裏簡單介紹一下路由、model和controller的一個分層開發實現:
首先,按照gin框架的基礎路由處理,調用Controller
中的登錄函數,接收處理路由的 GET 請求。
//main.go ... func main(){ r := gin.Default() account := new(controllers.AccountController) r.GET("/account/login", account.WxLogin) }
而後在Controller
中利用c.Query
讀取參數 jscode ,再將 jscode 和其餘信息一塊兒發送給微信服務器,得到官方返回的核心字段。
... //接受請求參數後進行處理 func (ctrl AccountController) WxLogin(c *gin.Context) { jscode := c.Query("jsCode") //發送jscode,得到用戶的open_id wxSession, err := accountModel.WxLogin(jscode) ... }
具體實現jscode發送和處理的邏輯在Model
中完成(順便吐槽一下golang的錯誤處理,寫了無數的if err!=nil
),還有golang 結構體中的tag十分好用,綁定數據庫讀寫實體、json序列化字段都能用一個結構體和不一樣tag靈活處理。
//WxSession 微信登錄接口返回session type WxSession struct { SessionKey string `json:"session_key"` ExpireIn int `json:"expires_in"` OpenID string `json:"openid"` } //WxLogin 微信用戶受權 func (m AccountModel) WxLogin(jscode string) (session WxSession, err error) { client := &http.Client{} //生成要訪問的url url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", "xxxYOUR APPIDxxx", "xxxYOUR SECRETxxx", jscode) //提交請求 reqest, err := http.NewRequest("GET", url, nil) if err != nil { panic(err) } //處理返回結果 response, _ := client.Do(reqest) body, err := ioutil.ReadAll(response.Body) jsonStr := string(body) //解析json if err := json.Unmarshal(body, &session); err != nil { session.SessionKey = jsonStr return session, err } return session, err }
返回的session中即包含open_id
拿到open_id後就能夠根據open_id去數據庫查詢所綁定的用戶id,獲得用戶身份,同時也須要根據用戶信息來生成token。而我須要的只是uid,其餘用戶信息的封裝原理相似,僅供參考。
jwt-go
是個功能強大的jwt生成包,封裝了不少定製化函數,能夠根據實際須要靈活的配置信息來生成符合要求的token字符串,而且提供了token自動驗證的功能,詳細說明見GitHub主頁:jwt-to
//SignWxToken 生成token,uid用戶id,expireSec過時秒數 func (u Util) SignWxToken(uid int64, expireSec int) (tokenStr string, err error) { // 帶權限建立令牌 claims := make(jwt.MapClaims) claims["uid"] = uid claims["admin"] = false sec := time.Duration(expireSec) claims["exp"] = time.Now().Add(time.Second * sec).Unix() //自定義有效期,過時須要從新登陸獲取token token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 使用自定義字符串加密 and get the complete encoded token as a string tokenStr, err = token.SignedString([]byte("xxxYOUR KEYxxx")) return tokenStr, err }
這裏生成標準格式的jwt字符串,將token下發到小程序客戶端後,客戶端將在每次請求攜帶此token,來進行身份校驗。這裏建議小程序端將全部的request進行集合封裝,以便於統一操做,這個項目開發完後也會對小程序端的一些操做進行總結歸納,這裏很少廢話,token須要存放在請求頭部的Authorization
中。
首先,在main.go
添加權限驗證路由組:
uAuth := r.Group("/xxx", jwtauth.WxAuth()) { ....... 這裏是全部須要用戶身份識別的路由 ....... }
而後寫驗證中間件:
type MyCustomClaims struct { UID int `json:"uid"` jwt.StandardClaims } //WxAuth ... func WxAuth() gin.HandlerFunc { return func(c *gin.Context) { authString := c.Request.Header.Get("Authorization") kv := strings.Split(authString, " ") if len(kv) != 2 || kv[0] != "Bearer" { result := models.UnauthorizedResult() c.JSON(200, result) c.Abort() return } tokenString := kv[1] // Parse token token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte("xxxYOUR KEYxxx"), nil }) if err != nil { result := models.UnauthorizedResult() c.JSON(200, result) c.Abort() return } if !token.Valid { result := models.UnauthorizedResult() c.JSON(200, result) c.Abort() return } claims, ok := token.Claims.(*MyCustomClaims) if !ok { c.JSON(403, result) c.Abort() return } //將uid寫入請求參數 uid := claims.UID c.Set("uid", uid) } }
其中result是本身封裝的一個返回值結構體,由於UID是本身額外封裝的參數,這裏根據jwt-go的說明,封裝了一個結構體存放和解析參數,實際上若是用原生默認方法也是能夠取到這個值,可是要添加額外的解析處理,這裏仍是推薦用封裝結構體的方法。最後token驗證成功的話,將uid寫入請求內部參數,供權限組內的路由函數使用,示例以下:
//List 列表 func (ctrl XXXController) List(c *gin.Context) { uidVal, ok := c.Get("uid") if ok { uid := uidVal.(int) .... 根據UID進行其餘操做 .... }
至此,一個簡單的從小程序登錄和api用戶認證的流程已經完成。
寫到這裏,發現這篇博文更像是一篇流水帳,把本身的實現邏輯和代碼記錄了一下,對詳細的實現原理和一些細節操做,沒有花很大篇幅去寫明白,緣由之一是精力有限,業務功能還沒作完,不太可能花不少時間來介紹一個登錄模塊,這裏只是簡單梳理進行經驗共享,主要緣由仍是本身功力不夠,對原理實現這一塊沒有深刻了解過,目前更多關注的只是業務實現,對golang的運行機制和一些包的使用原理了解不夠,這些都須要慢慢增強,但願之後能作作一點學習分享吧。這篇簡單介紹就到此結束了,感謝閱讀。