在完成中间件的介绍和日志中间件的代码后,我们的程序已经基本能正常跑通了,但如果要上生产,还少了一些必要的功能,例如鉴权、异常捕捉等。本章我们介绍如何编写鉴权中间件。

常用的无状态鉴权方式

  • 网络鉴权
  • 用户鉴权
  • 加密算法鉴权

Gin-IPs 鉴权访问

  • 鉴权算法介绍
  • 加密代码
// 签名算法如下
/*
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鉴权中间件使用
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 代码

09-03 06:03