github.com/godaddy-x/freego@v1.0.156/geetest/sdk/geetest_lib.go (about) 1 package sdk 2 3 import ( 4 "crypto/hmac" 5 "crypto/md5" 6 "crypto/sha256" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "io/ioutil" 11 "math/rand" 12 "net/http" 13 "net/url" 14 "strings" 15 "time" 16 ) 17 18 /** 19 * sdk lib包,核心逻辑。 20 * 21 * @author liuquan@geetest.com 22 */ 23 const ( 24 IS_DEBUG bool = true // 调试开关,是否输出调试日志 25 API_URL string = "http://api.geetest.com" 26 REGISTER_URL string = "/register.php" 27 VALIDATE_URL string = "/validate.php" 28 JSON_FORMAT string = "1" 29 NEW_CAPTCHA bool = true 30 HTTP_TIMEOUT_DEFAULT int = 5 // 单位:秒 31 VERSION string = "golang-gin:3.1.1" 32 GEETEST_CHALLENGE string = "geetest_challenge" // 极验二次验证表单传参字段 chllenge 33 GEETEST_VALIDATE string = "geetest_validate" // 极验二次验证表单传参字段 validate 34 GEETEST_SECCODE string = "geetest_seccode" // 极验二次验证表单传参字段 seccode 35 ) 36 37 type GeetestLib struct { 38 geetest_id string // 公钥 39 geetest_key string // 私钥 40 debug bool 41 libResult *GeetestLibResult 42 } 43 44 func NewGeetestLib(geetest_id, geetest_key string, debug bool) *GeetestLib { 45 return &GeetestLib{geetest_id, geetest_key, debug, NewGeetestLibResult()} 46 } 47 48 func (g *GeetestLib) gtlog(msg string) { 49 if g.debug { 50 fmt.Println("gtlog: " + msg) 51 } 52 } 53 54 /** 55 * 验证初始化 56 */ 57 func (g *GeetestLib) Register(digestmod string, params map[string]string) *GeetestLibResult { 58 g.gtlog(fmt.Sprintf("Register(): 开始验证初始化, digestmod=%s.", digestmod)) 59 origin_challenge := g.requestRegister(params) 60 g.buildRegisterResult(origin_challenge, digestmod) 61 g.gtlog(fmt.Sprintf("Register(): 验证初始化, lib包返回信息=%s.", g.libResult)) 62 return g.libResult 63 } 64 65 func (g *GeetestLib) LocalRegister() *GeetestLibResult { 66 g.gtlog(fmt.Sprintf("获取当前bypass状态为fail,后续流程走宕机模式")) 67 g.buildRegisterResult("", "") 68 g.gtlog(fmt.Sprintf("Register(): 验证初始化, lib包返回信息=%s.", g.libResult)) 69 return g.libResult 70 } 71 72 /** 73 * 向极验发送验证初始化的请求,GET方式 74 */ 75 func (g *GeetestLib) requestRegister(params map[string]string) string { 76 params["gt"] = g.geetest_id 77 params["json_format"] = JSON_FORMAT 78 params["sdk"] = VERSION 79 register_url := API_URL + REGISTER_URL 80 g.gtlog(fmt.Sprintf("requestRegister(): 验证初始化, 向极验发送请求, url=%s, params=%s.", register_url, params)) 81 resBody, err := g.httpGet(register_url, params) 82 if err != nil { 83 g.gtlog(fmt.Sprintf("requestRegister(): 验证初始化, 请求异常,后续流程走宕机模式, %s", err)) 84 return "" 85 } 86 g.gtlog(fmt.Sprintf("requestRegister(): 验证初始化, 与极验网络交互正常, 返回body=%s.", resBody)) 87 resMap := make(map[string]interface{}) 88 err = json.Unmarshal([]byte(resBody), &resMap) 89 if err != nil { 90 g.gtlog(fmt.Sprintf("requestRegister(): 验证初始化, 解析json异常,后续流程走宕机模式, %s", err)) 91 return "" 92 } 93 return resMap["challenge"].(string) 94 } 95 96 /** 97 * 构建验证初始化返回数据 98 */ 99 func (g *GeetestLib) buildRegisterResult(origin_challenge string, digestmod string) { 100 // origin_challenge为空或者值为0代表失败 101 if origin_challenge == "" || origin_challenge == "0" { 102 // 本地随机生成32位字符串 103 characters := []byte("0123456789abcdefghijklmnopqrstuvwxyz") 104 challenge := []byte{} 105 for i := 0; i < 32; i++ { 106 challenge = append(challenge, characters[rand.Intn(len(characters))]) 107 } 108 dataMap := map[string]interface{}{ 109 "success": 0, 110 "gt": g.geetest_id, 111 "challenge": string(challenge), 112 "new_captcha": NEW_CAPTCHA, 113 } 114 dataJson, _ := json.Marshal(dataMap) 115 g.libResult.setAll(0, string(dataJson), "获取当前bypass状态为fail,后续流程走宕机模式") 116 } else { 117 challenge := "" 118 if digestmod == "md5" { 119 challenge = g.md5_encode(origin_challenge + g.geetest_key) 120 } else if digestmod == "sha256" { 121 challenge = g.sha256_encode(origin_challenge + g.geetest_key) 122 } else if digestmod == "hmac-sha256" { 123 challenge = g.hmac_sha256_encode(origin_challenge, g.geetest_key) 124 } else { 125 challenge = g.md5_encode(origin_challenge + g.geetest_key) 126 } 127 dataMap := map[string]interface{}{ 128 "success": 1, 129 "gt": g.geetest_id, 130 "challenge": challenge, 131 "new_captcha": NEW_CAPTCHA, 132 } 133 dataJson, _ := json.Marshal(dataMap) 134 g.libResult.setAll(1, string(dataJson), "") 135 } 136 } 137 138 /** 139 * 正常流程下(即验证初始化成功),二次验证 140 */ 141 func (g *GeetestLib) SuccessValidate(challenge string, validate string, seccode string) *GeetestLibResult { 142 g.gtlog(fmt.Sprintf("SuccessValidate(): 开始二次验证 正常模式, challenge=%s, validate=%s, seccode=%s.", challenge, validate, seccode)) 143 if !g.checkParam(challenge, validate, seccode) { 144 g.libResult.setAll(0, "", "正常模式,本地校验,参数challenge、validate、seccode不可为空") 145 } else { 146 response_seccode := g.requestValidate(challenge, validate, seccode) 147 if response_seccode == "" { 148 g.libResult.setAll(0, "", "请求极验validate接口失败") 149 } else if response_seccode == "false" { 150 g.libResult.setAll(0, "", "极验二次验证不通过") 151 } else { 152 g.libResult.setAll(1, "", "") 153 } 154 } 155 g.gtlog(fmt.Sprintf("SuccessValidate(): 二次验证 正常模式, lib包返回信息=%s.", g.libResult)) 156 return g.libResult 157 } 158 159 /** 160 * 异常流程下(即验证初始化失败,宕机模式),二次验证 161 * 注意:由于是宕机模式,初衷是保证验证业务不会中断正常业务,所以此处只作简单的参数校验,可自行设计逻辑。 162 */ 163 func (g *GeetestLib) FailValidate(challenge string, validate string, seccode string) *GeetestLibResult { 164 g.gtlog(fmt.Sprintf("FailValidate(): 开始二次验证 宕机模式, challenge=%s, validate=%s, seccode=%s.", challenge, validate, seccode)) 165 if !g.checkParam(challenge, validate, seccode) { 166 g.libResult.setAll(0, "", "宕机模式,本地校验,参数challenge、validate、seccode不可为空.") 167 } else { 168 g.libResult.setAll(1, "", "") 169 } 170 g.gtlog(fmt.Sprintf("FailValidate(): 二次验证 宕机模式, lib包返回信息=%s.", g.libResult)) 171 return g.libResult 172 } 173 174 /** 175 * 向极验发送二次验证的请求,POST方式 176 */ 177 func (g *GeetestLib) requestValidate(challenge string, validate string, seccode string) string { 178 params := map[string]string{} 179 params["seccode"] = seccode 180 params["json_format"] = JSON_FORMAT 181 params["challenge"] = challenge 182 params["sdk"] = VERSION 183 params["captchaid"] = g.geetest_id 184 validate_url := API_URL + VALIDATE_URL 185 g.gtlog(fmt.Sprintf("requestValidate(): 二次验证 正常模式, 向极验发送请求, url=%s, params=%s.", validate_url, params)) 186 resBody, err := g.httpPost(validate_url, params) 187 if err != nil { 188 g.gtlog(fmt.Sprintf("requestValidate(): 二次验证 正常模式, 请求异常, %s", err)) 189 return "" 190 } 191 g.gtlog(fmt.Sprintf("requestValidate(): 二次验证 正常模式, 与极验网络交互正常, 返回body=%s.", resBody)) 192 resMap := make(map[string]interface{}) 193 err = json.Unmarshal([]byte(resBody), &resMap) 194 if err != nil { 195 g.gtlog(fmt.Sprintf("requestValidate(): 二次验证 正常模式, 解析json异常, %s", err)) 196 return "" 197 } 198 return resMap["seccode"].(string) 199 } 200 201 /** 202 * 校验二次验证的三个参数,校验通过返回true,校验失败返回false 203 */ 204 func (g *GeetestLib) checkParam(challenge string, validate string, seccode string) bool { 205 return !(challenge == "" || strings.TrimSpace(challenge) == "" || validate == "" || strings.TrimSpace(validate) == "" || seccode == "" || strings.TrimSpace(seccode) == "") 206 } 207 208 /** 209 * 发送GET请求,获取服务器返回结果 210 */ 211 func (g *GeetestLib) httpGet(getUrl string, params map[string]string) (string, error) { 212 q := url.Values{} 213 if params != nil { 214 for key, val := range params { 215 q.Add(key, val) 216 } 217 } 218 req, err := http.NewRequest(http.MethodGet, getUrl, nil) 219 if err != nil { 220 return "", errors.New("NewRequest fail") 221 } 222 req.URL.RawQuery = q.Encode() 223 client := &http.Client{Timeout: time.Duration(HTTP_TIMEOUT_DEFAULT) * time.Second} 224 res, err := client.Do(req) 225 if err != nil { 226 return "", err 227 } 228 defer res.Body.Close() 229 body, err := ioutil.ReadAll(res.Body) 230 if err != nil { 231 return "", err 232 } 233 if res.StatusCode == 200 { 234 return string(body), nil 235 } 236 return "", nil 237 } 238 239 /** 240 * 发送POST请求,获取服务器返回结果 241 */ 242 func (g *GeetestLib) httpPost(postUrl string, params map[string]string) (string, error) { 243 q := url.Values{} 244 if params != nil { 245 for key, val := range params { 246 q.Add(key, val) 247 } 248 } 249 req, err := http.NewRequest(http.MethodPost, postUrl, strings.NewReader(q.Encode())) 250 if err != nil { 251 return "", errors.New("NewRequest fail") 252 } 253 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 254 client := &http.Client{Timeout: time.Duration(HTTP_TIMEOUT_DEFAULT) * time.Second} 255 res, err := client.Do(req) 256 if err != nil { 257 return "", err 258 } 259 defer res.Body.Close() 260 body, err := ioutil.ReadAll(res.Body) 261 if err != nil { 262 return "", err 263 } 264 if res.StatusCode == 200 { 265 return string(body), nil 266 } 267 return "", nil 268 } 269 270 /** 271 * md5 加密 272 */ 273 func (g *GeetestLib) md5_encode(value string) string { 274 h := md5.New() 275 h.Write([]byte(value)) 276 return fmt.Sprintf("%x", h.Sum(nil)) 277 } 278 279 /** 280 * sha256加密 281 */ 282 func (g *GeetestLib) sha256_encode(value string) string { 283 h := sha256.New() 284 h.Write([]byte(value)) 285 return fmt.Sprintf("%x", h.Sum(nil)) 286 } 287 288 /** 289 * hmac-sha256 加密 290 */ 291 func (g *GeetestLib) hmac_sha256_encode(value string, key string) string { 292 h := hmac.New(sha256.New, []byte(key)) 293 h.Write([]byte(value)) 294 return fmt.Sprintf("%x", h.Sum(nil)) 295 }