go gin中間件開發

go gin中間件開發

我開發了檢查request請求中的參數(包含get post和json參數)sql注入檢查,和token檢查

  • 我先說一下思路
    1. 在中間件中,獲得request,取出其中你要檢查或過濾的參數
    2. token檢查,我有一張uid->token的數據表,用獲得的token去查詢數據庫,檢查是否存在
    3. sql注入檢查,使用正則表達式匹配每一個參數,注意json參數和postget請求的參數,獲取方式不一樣,具體看代碼
    4. 做一個slice和map,在匹配到slice或map中的參數或url時,跳過檢查

token檢查

  • 需要過濾的url和參數
var skipUrlForTokenArr = []string{
	"/test",
	"/public/",
	"/user/login",
	"/user/register",
	"/user/token/refresh",
	"/user/wxopendata/decode",
	"/user/wxserver/connect",
}

var maybeCheckForTokenArr = []string{
	"/user/homepage",
}

var skipParamsForSQLInjectMap = map[string]int{
	"file":           1,
	"encrypt_data":   1,
	"openid":         1,
	"iv":             1,
	"name":           1,
	"link":           1,
	"head_pic_link":  1,
	"pic_link":       1,
	"id_front_pic":   1,
	"id_reverse_pic": 1,
	"education_pic":  1,

  • 檢查token並獲得token對應的user信息

type User struct {
	Uid      int
	Level    int
	Phone    string
	Nickname string
	Sex      int
}

func CheckToken() gin.HandlerFunc {
	return func(c *gin.Context) {
		for _, url := range skipUrlForTokenArr {
			if strings.Contains(c.Request.URL.Path, url) {
				c.Next()
				return
			}
		}

		token := c.GetHeader("token")
		if token == "" {
			for _, url := range maybeCheckForTokenArr {
				if strings.Contains(c.Request.URL.Path, url) {
					c.Next()
					return
				}
			}
			err := errors.New("invalid token")
			tools.Mlog.Error(err.Error())
			c.JSON(http.StatusBadRequest, code.GeneralErrRet(err))
			c.Abort()
			return
		}
		fmt.Printf("token = %s\n", token)
		if tools.FilteredSQLInject(token) {
			err := fmt.Errorf("sql注入攻擊 %s", token)
			tools.Mlog.Error(err.Error())
			c.JSON(http.StatusBadRequest, code.GeneralErrRet(err))
			c.Abort()
			return
		}
		//檢查token是否存在
		uid := -1
		var tokenCreateAt time.Time
		row := db.DbApp.QueryRow(`SELECT uid, token_create_at FROM user_token WHERE token = ?;`, token)
		err := row.Scan(&uid, &tokenCreateAt)
		if err != nil && err != sql.ErrNoRows {
			tools.Mlog.Error(err.Error())
			c.JSON(http.StatusInternalServerError, code.GeneralErrRet(err))
			c.Abort()
		}
		if uid == -1 {
			c.JSON(http.StatusOK, code.RetTokenIsntExists)
			c.Abort()
			return
		}
		//token是否過期
		//m, _ := time.ParseDuration(fmt.Sprintf("%dm", setting.TokenConf.TokenExp))
		//if tokenCreateAt.Add(m).Before(time.Now()) {
		//	err = errors.New("token is expired")
		//	c.JSON(http.StatusUnauthorized, code.GeneralErrRet(err))
		//	c.Abort()
		//	return
		//}

		var u User
		row = db.DbApp.QueryRow(`SELECT level, phone, nickname, sex FROM user WHERE uid = ?;`, uid)
		u.Uid = uid
		err = row.Scan(&u.Level, &u.Phone, &u.Nickname, &u.Sex)
		if err != nil {
			tools.Mlog.Error(err.Error())
			c.JSON(http.StatusInternalServerError, code.GeneralErrRet(err))
			c.Abort()
			return
		}
		c.Set("uid", u.Uid)
		c.Set("user", u)
		c.Next()
	}

檢查sql注入

  • 臨時copy一份request出來,這裏不能讀取原裝request中的數據,因爲body讀取過一次後,它會自動關閉true代表body不關閉
func CheckSQLInject() gin.HandlerFunc {
	return func(c *gin.Context) {
		body, err := httputil.DumpRequest(c.Request, true)
		if err != nil {
			err = fmt.Errorf("parse request body failed, err: %s", err)
			tools.Mlog.Error(err.Error())
			c.JSON(http.StatusInternalServerError, code.GeneralErrRet(err))
			c.Abort()
			return
		}
		//fmt.Printf("%s\n", string(body))
		//fmt.Printf("%#v\n", c.Request)
		if bytes.Index(body, []byte("application/json")) != -1 { //檢查json參數
			//過濾http頭部,獲取body
			body = body[bytes.Index(body, []byte("User-Agent")):]
			index := bytes.Index(body, []byte("{"))
			if index != -1 { //判讀是否找到body
				body = body[index:]
			} else {
				body = nil
			}
			if len(body) != 0 { //檢查request json參數
				fmt.Println(string(body))
				m := make(map[string]interface{})
				err = json.Unmarshal(body, &m)
				if err != nil {
					err = fmt.Errorf("parse request body failed, err: %s", err.Error())
					tools.Mlog.Error(err.Error())
					c.JSON(http.StatusInternalServerError, code.GeneralErrRet(err))
					c.Abort()
					return
				}
				for k, v := range m {
					if skipParamsForSQLInjectMap[k] == 1 {
						continue
					}
					if reflect.TypeOf(v).String() == "string" {
						if tools.FilteredSQLInject(v.(string)) {
							err := fmt.Errorf("sql注入攻擊 %s", v)
							tools.Mlog.Error(err.Error())
							c.JSON(http.StatusBadRequest, code.GeneralErrRet(err))
							c.Abort()
							return
						}
					}
				}
			}
		}
		if c.Request.Form == nil { //檢查get和post中的參數
			c.Request.ParseMultipartForm(32 << 20)
		}
		for k, arr := range c.Request.Form {
			if c.Request.Method != http.MethodGet {
				fmt.Printf("%s=%v&", k, arr)
			}
			if skipParamsForSQLInjectMap[k] == 1 {
				continue
			}
			for _, v := range arr {
				if tools.FilteredSQLInject(v) {
					err := errors.New("sql注入攻擊")
					tools.Mlog.Error(err.Error())
					c.JSON(http.StatusBadRequest, code.GeneralErrRet(err))
					c.Abort()
					return
				}

			}
		}
		c.Next()
	}
}

使用

router.Use(middleware.CheckToken(), middleware.CheckSQLInject())
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章