1. 程式人生 > >【Gin-API系列】Gin中介軟體之鑑權訪問(五)

【Gin-API系列】Gin中介軟體之鑑權訪問(五)

在完成中介軟體的介紹和日誌中介軟體的程式碼後,我們的程式已經基本能正常跑通了,但如果要上生產,還少了一些必要的功能,例如鑑權、異常捕捉等。本章我們介紹如何編寫鑑權中介軟體。 > 鑑權訪問,說白了就是給使用者的請求增加一些限制條件,過濾掉不符合要求的請求。完善的鑑權模組可以讓我們的服務跑得更加安全,特別是面向公共的服務。 # 常用的無狀態鑑權方式 * 網路鑑權 > 通常有IP白名單方式,通過獲取客戶端的真實IP來對請求進行過濾 * 使用者鑑權 > 通過賬號密碼或者分配的金鑰、Token等方式進行認證,常用的cookies、oauth2.0都是這種方式 * 加密演算法鑑權 > 客戶端使用加密演算法對使用者的引數進行計算加密得到Token,並將引數和Token一起傳送;服務端使用同樣的加密演算法對請求引數進行加密後比較Token的值是否一致。 # Gin-IPs 鑑權訪問 > 為了讓我們的服務更加安全,我們通常是混合多種鑑權方式使用。本文采取“使用者鑑權”和“加密演算法鑑權”混合方式。 * 鑑權演算法介紹 > 通過某種方式生成或者手動指定分配公鑰和私鑰對給使用者,使用者使用公鑰和請求引數組成訊息內容,使用私鑰對訊息內容進行雜湊計算,得到固定長度的字串。 伺服器使用同樣的方式對使用者請求的引數進行雜湊計算後比較。由於這裡面的演算法和金鑰對都是私有的,所以安全性較高,適用於大多數場景。 * 加密程式碼 ```golang // 簽名演算法如下 /* Signature = HMAC-SHA1('SecretKey', UTF-8-Encoding-Of( StringToSign ) ) ); StringToSign = method + "\n" + URL + "\n" + Sort-UrlParams + "\n" + Content-MD5 + "\n" + // md5(params) Expires + "\n" + AccessKey; */ func genSignature(accessKey, secretKey, uri, method, urlParams, params, nowTS string) (string, error) { if params != "" { md5Ctx := md5.New() _, _ = io.WriteString(md5Ctx, params) params = fmt.Sprintf("%x", md5Ctx.Sum(nil)) } // HTTP-Verb + "\n" +URL + "\n" +Parameters + "\n" +Content-Type + "\n" +Content-MD5 + "\n" +Date + "\n" +AccessKey; strSign := method + "\n" + uri + "\n" + urlParams + "\n" + "\n" + params + "\n" + nowTS + "\n" + accessKey sign := hmacSHA1Encrypt(strSign, secretKey) return sign, nil } // hmacSHA1Encrypt encrypt the encryptText use encryptKey func hmacSHA1Encrypt(encryptText, encryptKey string) string { key := []byte(encryptKey) mac := hmac.New(sha1.New, key) mac.Write([]byte(encryptText)) var str = hex.EncodeToString(mac.Sum(nil)) return str } ``` * Gin鑑權中介軟體使用 ```golang func Validate() gin.HandlerFunc { return func(c *gin.Context) { response := route_response.Response{} response.Data.List = []interface{}{} // 初始化為空切片,而不是空引用 uri := c.Request.URL.Path contentType := c.Request.Header.Get("Content-Type") accessKey := c.DefaultQuery("accesskey", "") expires := c.DefaultQuery("expires", "") signature := c.DefaultQuery("signature", "") secret, err := dao.FetchSecret(accessKey) if err != nil || "valid" != secret.State { c.Abort() response.Code, response.Message = configure.RequestKeyNotFound, "無效的Token" c.JSON(http.StatusUnauthorized, response) return } secretKey := secret.SecretKey if nowTs, err := strconv.ParseInt(expires, 10, 64); err != nil { c.Abort() response.Code, response.Message = configure.RequestParameterTypeError, "有效期引數型別錯誤" c.JSON(http.StatusUnauthorized, response) return } else { passTime := time.Now().Unix() - nowTs if passTime < 0 || passTime >= configure.GinConfigValue.Expires { c.Abort() response.Code, response.Message = configure.RequestExpired, "請求已過期" c.JSON(http.StatusUnauthorized, response) return } } method := strings.ToUpper(c.Request.Method) var urlParams, params string if "POST" == method || "PUT" == method { body, _ := ioutil.ReadAll(c.Request.Body) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) // 重設body params = string(body) } else if "GET" == method || "DELETE" == method { queryParams := c.Request.URL.Query() allParams := make(map[string]string) for k, v := range queryParams { if k != "accesskey" && k != "expires" && k != "signature" { allParams[k] = v[0] // 如果某個key傳入了2個,只用第一個的值 } } keys := getMapKeysSorted(allParams) for _, k := range keys { urlParams += k + allParams[k] } } if signatureString, err := genSignature(accessKey, secretKey, uri, method, urlParams, params, expires); err != nil { c.Abort() response.Code, response.Message = configure.ApiGenSignatureError, "API內部錯誤" c.JSON(http.StatusUnauthorized, response) return } else { if signature != signatureString { c.Abort() response.Code, response.Message = configure.RequestAuthorizedFailed, "API認證失敗" c.JSON(http.StatusUnauthorized, response) return } } c.Next() } } ``` 本文關於鑑權訪問的介紹和使用到此為止,下一章我們將使用異常捕捉中介軟體來完善我們的程式。 ## Github 程式碼 > 請訪問 [Gin-IPs](https://github.com/AutoBingo/Gin-IPs.git) 或者搜尋