فهرست منبع

fix:签名方式变更

Roc 1 سال پیش
والد
کامیت
417fda0a40
4فایلهای تغییر یافته به همراه251 افزوده شده و 15 حذف شده
  1. 216 13
      controllers/base_auth.go
  2. 8 0
      models/db.go
  3. 22 0
      models/open_api_user.go
  4. 5 2
      utils/config.go

+ 216 - 13
controllers/base_auth.go

@@ -1,12 +1,20 @@
 package controllers
 
 import (
+	"crypto/md5"
 	"encoding/json"
+	"errors"
 	"fmt"
+	"github.com/beego/beego/v2/server/web"
+	"github.com/shopspring/decimal"
+	"math"
 	"net/http"
 	"net/url"
-
-	"github.com/beego/beego/v2/server/web"
+	"reflect"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
 
 	"hongze/hz_data_api/models"
 	"hongze/hz_data_api/utils"
@@ -36,16 +44,9 @@ func (this *BaseAuthController) Prepare() {
 	fmt.Println("Url:", uri)
 	if method != "HEAD" {
 		if method == "POST" {
-			authorization := this.Ctx.Input.Header("authorization")
-			if authorization == "" {
-				this.JSON(models.BaseResponse{Ret: 408, Msg: "请重新授权!", ErrMsg: "请重新授权:authorization is empty "}, false, false)
-				this.StopRun()
-				return
-			}
-			checkAuthorization := utils.MD5(utils.APP_NAME_EN + utils.Md5Key)
-			fmt.Println("checkAuthorization:", checkAuthorization)
-			if authorization != checkAuthorization {
-				this.JSON(models.BaseResponse{Ret: 408, Msg: "签名错误!", ErrMsg: "签名错误:authorization is err "}, false, false)
+			ok, errMsg := checkSign(this)
+			if !ok {
+				this.JSON(models.BaseResponse{Ret: 408, Msg: "签名错误!", ErrMsg: errMsg}, false, false)
 				this.StopRun()
 				return
 			}
@@ -61,6 +62,78 @@ func (this *BaseAuthController) Prepare() {
 	}
 }
 
+func checkSign(c *BaseAuthController) (ok bool, errMsg string) {
+	method := c.Ctx.Input.Method()
+	signData := make(map[string]string)
+
+	switch method {
+	case "GET":
+		//requestBody = c.Ctx.Request.RequestURI
+		params := c.Ctx.Request.URL.Query()
+		signData = convertParam(params)
+	case "POST":
+		//requestBody, _ = url.QueryUnescape(string(c.Ctx.Input.RequestBody))
+
+		//请求类型
+		contentType := c.Ctx.Request.Header.Get("content-type")
+		//fmt.Println("contentType:", contentType)
+		//fmt.Println("c.Ctx.Input.RequestBody:", string(c.Ctx.Input.RequestBody))
+
+		switch contentType {
+		case "multipart/form-data":
+			//文件最大5M
+			err := c.Ctx.Request.ParseMultipartForm(-int64(5 << 20))
+			if err != nil {
+				errMsg = fmt.Sprintf("获取参数失败,%v", err)
+				return
+			}
+			params := c.Ctx.Request.Form
+			signData = convertParam(params)
+		case "application/x-www-form-urlencoded":
+			err := c.Ctx.Request.ParseForm()
+			if err != nil {
+				errMsg = fmt.Sprintf("获取参数失败,%v", err)
+				return
+			}
+			params := c.Ctx.Request.Form
+			signData = convertParam(params)
+		case "application/json":
+			//var v interface{}
+			params := make(map[string]interface{})
+			err := json.Unmarshal(c.Ctx.Input.RequestBody, &params)
+			if err != nil {
+				errMsg = fmt.Sprintf("获取参数失败,%v", err)
+				return
+			}
+			//fmt.Println("params:", params)
+
+			signData = convertParamInterface(params)
+			//tmpV := v.(map[string]string)
+			//fmt.Println("tmpV:", tmpV)
+			//fmt.Sprintln("list type is v%", tmpV["list"])
+		default: //正常应该是其他方式获取解析的,暂时这么处理吧
+			err := c.Ctx.Request.ParseForm()
+			if err != nil {
+				errMsg = fmt.Sprintf("获取参数失败,%v", err)
+				return
+			}
+			params := c.Ctx.Request.Form
+			signData = convertParam(params)
+		}
+	}
+
+	// 开始校验数据
+	ip := c.Ctx.Input.IP()
+	err := checkSignData(signData, ip)
+	if err != nil {
+		errMsg = fmt.Sprintf("签名校验失败,%v", err)
+		return
+	}
+
+	ok = true
+	return
+}
+
 func (c *BaseAuthController) ServeJSON(encoding ...bool) {
 	// 方法处理完后,需要后置处理的业务逻辑
 	//if handlerList, ok := AfterHandlerUrlMap[c.Ctx.Request.URL.Path]; ok {
@@ -123,9 +196,139 @@ func (c *BaseAuthController) JSON(data interface{}, hasIndent bool, coding bool)
 	if requestBody == "" {
 		requestBody = c.Ctx.Input.URI()
 	}
-	apiLog.Println("请求地址:", c.Ctx.Input.URI(), "Authorization:", c.Ctx.Input.Header("Authorization"), "RequestBody:", requestBody, "ResponseBody", string(content), "IP:", ip)
+	apiLog.Println("请求地址:", c.Ctx.Input.URI(), "RequestBody:", requestBody, "ResponseBody", string(content), "IP:", ip)
 	if coding {
 		content = []byte(utils.StringsToJSON(string(content)))
 	}
 	return c.Ctx.Output.Body(content)
 }
+
+// 将请求传入的数据格式转换成签名需要的格式
+func convertParam(params map[string][]string) (signData map[string]string) {
+	signData = make(map[string]string)
+	for key := range params {
+		signData[key] = params[key][0]
+	}
+	return signData
+}
+
+// 将请求传入的数据格式转换成签名需要的格式(目前只能处理简单的类型,数组、对象暂不支持)
+func convertParamInterface(params map[string]interface{}) (signData map[string]string) {
+	signData = make(map[string]string)
+	for key := range params {
+		val := ``
+		//fmt.Println("key", key, ";val:", params[key], ";type:", reflect.TypeOf(params[key]))
+		//signData[key] = params[key][0]
+		tmpVal := params[key]
+		switch reflect.TypeOf(tmpVal).Kind() {
+		case reflect.String:
+			val = fmt.Sprint(tmpVal)
+		case reflect.Int, reflect.Int16, reflect.Int64, reflect.Int32, reflect.Int8:
+			val = fmt.Sprint(tmpVal)
+		case reflect.Uint, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint64:
+			val = fmt.Sprint(tmpVal)
+		case reflect.Bool:
+			val = fmt.Sprint(tmpVal)
+		case reflect.Float64:
+			decimalNum := decimal.NewFromFloat(tmpVal.(float64))
+			val = decimalNum.String()
+			//val = strconv.FormatFloat(tmpVal.(float64), 'E', -1, 64) //float64
+		case reflect.Float32:
+			decimalNum := decimal.NewFromFloat32(tmpVal.(float32))
+			val = decimalNum.String()
+		}
+		signData[key] = val
+	}
+	return signData
+}
+
+// checkSignData 请求参数签名校验
+func checkSignData(postData map[string]string, ip string) (err error) {
+	isSandbox := postData["is_sandbox"]
+	//如果是测试环境,且是沙箱环境的话,那么绕过测试
+	if utils.RunMode == "debug" && isSandbox != "" {
+		return
+	}
+
+	appid := postData["appid"]
+	if appid == "" {
+		err = errors.New("参数异常,缺少appid")
+		return
+	}
+	openApiUserInfo, tmpErr := models.GetByAppid(appid)
+	if tmpErr != nil {
+		if tmpErr.Error() == utils.ErrNoRow() {
+			err = errors.New("appid异常,请联系管理员")
+		} else {
+			err = errors.New("系统异常,请联系管理员")
+		}
+		return
+	}
+
+	if openApiUserInfo == nil {
+		err = errors.New("系统异常,请联系管理员")
+		return
+	}
+	//如果有ip限制,那么就添加ip
+	if openApiUserInfo.Ip != "" {
+		if !strings.Contains(openApiUserInfo.Ip, ip) {
+			err = errors.New(fmt.Sprintf("无权限访问该接口,ip:%v,请联系管理员", ip))
+			return
+		}
+	}
+
+	//接口提交的签名字符串
+	ownSign := postData["sign"]
+	if ownSign == "" {
+		err = errors.New("参数异常,缺少签名字符串")
+		return
+	}
+	if postData["nonce_str"] == "" {
+		err = errors.New("参数异常,缺少随机字符串")
+		return
+	}
+	if postData["timestamp"] == "" {
+		err = errors.New("参数异常,缺少时间戳")
+		return
+	} else {
+		timeUnix := time.Now().Unix() //当前格林威治时间,int64类型
+		//将接口传入的时间做转换
+		timestamp, timeErr := strconv.ParseInt(postData["timestamp"], 10, 64)
+		if timeErr != nil {
+			err = errors.New("参数异常,时间戳格式异常")
+			return
+		}
+		if math.Abs(float64(timeUnix-timestamp)) > 300 {
+			err = errors.New("当前时间异常,请调整设备时间与北京时间一致")
+			return
+		}
+	}
+
+	//先取出除sign外的所有的提交的参数key
+	var keys []string
+	for k := range postData {
+		if k != "sign" {
+			keys = append(keys, k)
+		}
+	}
+
+	//1,根据参数名称的ASCII码表的顺序排序
+	sort.Strings(keys)
+
+	//2 根据排序后的参数名称,取出对应的值,并拼接字符串
+	var signStr string
+	for _, v := range keys {
+		signStr += v + "=" + postData[v] + "&"
+	}
+	//3,全转小写(md5(拼装的字符串后+分配给你的app_secret))
+	//sign := strings.ToLower(fmt.Sprintf("%x", md5.Sum([]byte(strings.Trim(signStr, "&")+key))))
+
+	//md5.Sum([]byte(signStr+"key="+key))  这是md5加密出来后的每个字符的ascall码,需要再转换成对应的字符
+	//3,全转大写(md5(拼装的字符串后+分配给你的app_secret))
+	sign := strings.ToUpper(fmt.Sprintf("%x", md5.Sum([]byte(signStr+"secret="+openApiUserInfo.Secret))))
+	if sign != ownSign {
+		utils.FileLog.Info(fmt.Sprintf("签名校验异常,签名字符串:%v;服务端签名值:%v", signStr, sign))
+		return errors.New("签名校验异常,请核实签名")
+	}
+	return nil
+}

+ 8 - 0
models/db.go

@@ -15,6 +15,14 @@ func init() {
 	db, _ := orm.GetDB("default")
 	db.SetConnMaxLifetime(10 * time.Minute)
 
+	// 用户主库
+	_ = orm.RegisterDataBase("weekly", "mysql", utils.MYSQL_WEEKLY_URL)
+	orm.SetMaxIdleConns("weekly", 50)
+	orm.SetMaxOpenConns("weekly", 100)
+
+	weeklyDb, _ := orm.GetDB("weekly")
+	weeklyDb.SetConnMaxLifetime(10 * time.Minute)
+
 	orm.Debug = true
 	orm.DebugLog = orm.NewLog(utils.Binlog)
 

+ 22 - 0
models/open_api_user.go

@@ -0,0 +1,22 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type OpenApiUser struct {
+	Appid      string `orm:"column(appid);pk" json:"appid" description:"开放平台appid"`
+	Secret     string `orm:"column(secret);" json:"secret" description:"开放平台秘钥"`
+	Ip         string `orm:"column(ip);" json:"ip" description:"限制请求来源ip,多个ip用英文,隔开"`
+	Remark     string `orm:"column(remark);" json:"remark" description:"备注信息"`
+	CreateTime string `orm:"column(create_time);" json:"create_time" description:"创建时间"`
+	ModifyTime string `orm:"column(modify_time);" json:"modify_time" description:"最近一次更新时间"`
+}
+
+// GetByAppid 根据appid获取开放api用户信息
+func GetByAppid(appid string) (item *OpenApiUser, err error) {
+	sql := `SELECT * FROM open_api_user WHERE appid=? LIMIT 1`
+	o := orm.NewOrmUsingDB("weekly")
+	err = o.Raw(sql, appid).QueryRow(&item)
+	return
+}

+ 5 - 2
utils/config.go

@@ -8,8 +8,9 @@ import (
 )
 
 var (
-	RunMode   string //运行模式
-	MYSQL_URL string //数据库连接
+	RunMode          string //运行模式
+	MYSQL_URL        string //数据库连接
+	MYSQL_WEEKLY_URL string //用户主库
 
 	REDIS_CACHE string       //缓存地址
 	Rc          *cache.Cache //redis缓存
@@ -50,6 +51,8 @@ func init() {
 	}
 	beeLogger.Log.Info(RunMode + " 模式")
 	MYSQL_URL = config["mysql_url"]
+	// 用户主库
+	MYSQL_WEEKLY_URL = config["mysql_url_weekly"]
 
 	REDIS_CACHE = config["beego_cache"]
 	if len(REDIS_CACHE) <= 0 {