github.com/godaddy-x/freego@v1.0.156/geetest/geetest_controller.go (about) 1 package geetest 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "github.com/godaddy-x/freego/cache" 8 "github.com/godaddy-x/freego/ex" 9 "github.com/godaddy-x/freego/geetest/sdk" 10 "github.com/godaddy-x/freego/node" 11 "github.com/godaddy-x/freego/utils" 12 "github.com/godaddy-x/freego/zlog" 13 "io/ioutil" 14 "net/http" 15 "net/url" 16 "time" 17 ) 18 19 var ( 20 config = Config{} 21 ) 22 23 type Config struct { 24 GeetestID string `json:"geetestID"` 25 GeetestKey string `json:"geetestKey"` 26 Debug bool `json:"debug"` 27 Boundary int `json:"boundary"` // 界限值0 - 100 28 } 29 30 // 发送GET请求 31 func httpGet(getURL string, params map[string]string) (string, error) { 32 q := url.Values{} 33 if params != nil { 34 for key, val := range params { 35 q.Add(key, val) 36 } 37 } 38 req, err := http.NewRequest(http.MethodGet, getURL, nil) 39 if err != nil { 40 return "", errors.New("NewRequest fail") 41 } 42 req.URL.RawQuery = q.Encode() 43 client := &http.Client{Timeout: time.Duration(5) * time.Second} 44 res, err := client.Do(req) 45 if err != nil { 46 return "", err 47 } 48 defer res.Body.Close() 49 body, err := ioutil.ReadAll(res.Body) 50 if err != nil { 51 return "", err 52 } 53 if res.StatusCode == 200 { 54 return string(body), nil 55 } 56 return "", nil 57 } 58 59 // 从geetest获取bypass状态 60 func CheckServerStatus(conf Config) { 61 if len(conf.GeetestID) == 0 || len(conf.GeetestKey) == 0 { 62 return 63 } 64 config = conf 65 redisStatus := "fail" 66 for true { 67 params := make(map[string]string) 68 params["gt"] = config.GeetestID 69 resBody, err := httpGet(BYPASS_URL, params) 70 if resBody == "" { 71 redisStatus = "fail" 72 } else { 73 resMap := make(map[string]interface{}) 74 err = json.Unmarshal([]byte(resBody), &resMap) 75 if err != nil { 76 redisStatus = "fail" 77 } 78 if resMap["status"] == "success" { 79 redisStatus = "success" 80 } else { 81 redisStatus = "fail" 82 } 83 } 84 client, err := cache.NewRedis() 85 if err != nil { 86 fmt.Println(err) 87 return 88 } 89 if err := client.Put(GEETEST_BYPASS_STATUS_KEY, redisStatus); err != nil { 90 fmt.Println(err) 91 return 92 } 93 if config.Debug { 94 fmt.Println("bypass状态已经获取并存入redis,当前状态为-", redisStatus) 95 } 96 time.Sleep(time.Duration(CYCLE_TIME) * time.Second) 97 } 98 } 99 100 // 获取redis缓存的bypass状态 101 func GetBypassCache() (status string) { 102 client, err := cache.NewRedis() 103 if err != nil { 104 zlog.Error("cache client failed", 0, zlog.AddError(err)) 105 return "" 106 } 107 status, err = client.GetString(GEETEST_BYPASS_STATUS_KEY) 108 if err != nil { 109 zlog.Error("cache client getString failed", 0, zlog.AddError(err)) 110 return "" 111 } 112 return status 113 } 114 115 // 验证初始化接口,GET请求 116 func FirstRegister(ctx *node.Context) (sdk.GeetestLibResultData, error) { 117 /* 118 必传参数 119 digestmod 此版本sdk可支持md5、sha256、hmac-sha256,md5之外的算法需特殊配置的账号,联系极验客服 120 自定义参数,可选择添加 121 user_id 客户端用户的唯一标识,确定用户的唯一性;作用于提供进阶数据分析服务,可在register和validate接口传入,不传入也不影响验证服务的使用;若担心用户信息风险,可作预处理(如哈希处理)再提供到极验 122 client_type 客户端类型,web:电脑上的浏览器;h5:手机上的浏览器,包括移动应用内完全内置的web_view;native:通过原生sdk植入app应用的方式;unknown:未知 123 ip_address 客户端请求sdk服务器的ip地址 124 */ 125 filterObject, err := GetFilterObject(ctx) 126 if err != nil { 127 return sdk.GeetestLibResultData{}, ex.Throw{Msg: err.Error()} 128 } 129 client, err := cache.NewRedis() 130 if err != nil { 131 return sdk.GeetestLibResultData{}, ex.Throw{Msg: err.Error()} 132 } 133 // 判定是否触发验证机制, 如已经有触发验证则不能跳过, 1-100范围,命中数值>Boundary则不触发验证 134 _, status := ValidStatusCode(filterObject, 1) 135 if (len(config.GeetestID) == 0 && len(config.GeetestKey) == 0) || (status != 1 && utils.CheckRangeInt(config.Boundary, 1, 100) && utils.ModRand(100) > config.Boundary) { 136 if err := client.Put(utils.AddStr("geetest.", filterObject), 2, 1800); err != nil { 137 return sdk.GeetestLibResultData{}, ex.Throw{Msg: err.Error()} 138 } 139 return sdk.GeetestLibResultData{Status: 1}, nil 140 } 141 digestmod := "hmac-sha256" 142 params := map[string]string{ 143 "digestmod": digestmod, 144 "user_id": filterObject, 145 "client_type": "web", 146 "ip_address": ctx.RequestCtx.LocalAddr().String(), 147 } 148 gtLib := sdk.NewGeetestLib(config.GeetestID, config.GeetestKey, config.Debug) 149 var result *sdk.GeetestLibResult 150 if GetBypassCache() == "success" { 151 result = gtLib.Register(digestmod, params) 152 } else { 153 result = gtLib.LocalRegister() 154 } 155 if err := client.Put(utils.AddStr("geetest.", filterObject), 1, 1800); err != nil { 156 return sdk.GeetestLibResultData{}, ex.Throw{Msg: err.Error()} 157 } 158 bs := utils.Str2Bytes(result.Data) 159 return sdk.GeetestLibResultData{ 160 Challenge: utils.GetJsonString(bs, "challenge"), 161 Gt: utils.GetJsonString(bs, "gt"), 162 NewCaptcha: utils.GetJsonBool(bs, "new_captcha"), 163 Success: utils.GetJsonInt(bs, "success"), 164 }, nil 165 } 166 167 // 二次验证接口,POST请求 168 func SecondValidate(ctx *node.Context) (map[string]string, error) { 169 filterObject, err := GetFilterObject(ctx) 170 if err != nil { 171 return nil, ex.Throw{Msg: err.Error()} 172 } 173 b, status := ValidStatusCode(filterObject, 1) 174 if !b { 175 return nil, ex.Throw{Msg: "validator not initialized"} 176 } 177 if status == 2 { 178 return map[string]string{"result": "success"}, nil 179 } 180 challenge := utils.GetJsonString(ctx.JsonBody.RawData(), "geetest_challenge") 181 validate := utils.GetJsonString(ctx.JsonBody.RawData(), "geetest_validate") 182 seccode := utils.GetJsonString(ctx.JsonBody.RawData(), "geetest_seccode") 183 if len(challenge) == 0 || len(validate) == 0 || len(seccode) == 0 { 184 return nil, ex.Throw{Msg: "challenge/validate/seccode parameters is nil"} 185 } 186 gtLib := sdk.NewGeetestLib(config.GeetestID, config.GeetestKey, config.Debug) 187 bypassStatus := GetBypassCache() 188 var result *sdk.GeetestLibResult 189 if bypassStatus == "success" { 190 result = gtLib.SuccessValidate(challenge, validate, seccode) 191 } else { 192 result = gtLib.FailValidate(challenge, validate, seccode) 193 } 194 if result.Status != 1 { 195 return nil, ex.Throw{Msg: result.Msg} 196 } 197 client, err := cache.NewRedis() 198 if err != nil { 199 return nil, ex.Throw{Msg: err.Error()} 200 } 201 if err := client.Put(utils.AddStr("geetest.", filterObject), 2, 1800); err != nil { // 设置验证成功状态 202 return nil, ex.Throw{Msg: err.Error()} 203 } 204 return map[string]string{"result": "success"}, nil 205 } 206 207 func GetFilterObject(ctx *node.Context) (string, error) { 208 filterMethod := utils.GetJsonString(ctx.JsonBody.RawData(), "filterMethod") 209 filterObject := utils.GetJsonString(ctx.JsonBody.RawData(), "filterObject") 210 if len(filterMethod) == 0 || len(filterObject) == 0 { 211 return "", utils.Error("filterMethod or filterObject is nil") 212 } 213 return CreateFilterObject(filterMethod, filterObject), nil 214 } 215 216 func CreateFilterObject(filterMethod, filterObject string) string { 217 return utils.MD5(utils.AddStr(filterMethod, filterObject)) 218 } 219 220 // 验证状态码 221 func ValidStatusCode(filterObject string, statusCode int64) (bool, int64) { 222 client, err := cache.NewRedis() 223 if err != nil { 224 return false, 0 225 } 226 status, err := client.GetInt64(utils.AddStr("geetest.", filterObject)) 227 if err != nil { 228 return false, 0 229 } 230 if statusCode == 1 { // 是否可以进行二次验证 231 return status == 1 || status == 2, status 232 } 233 if statusCode == 2 { // 是否可以验证成功状态 234 return status == statusCode, status 235 } 236 return false, 0 237 } 238 239 // 验证完成状态 240 func ValidSuccess(filterObject string) bool { 241 b, _ := ValidStatusCode(filterObject, 2) 242 return b 243 } 244 245 // 清除验证状态 246 func CleanStatus(filterObject string) error { 247 client, err := cache.NewRedis() 248 if err != nil { 249 return ex.Throw{Msg: err.Error()} 250 } 251 if err := client.Del(utils.AddStr("geetest.", filterObject)); err != nil { 252 return ex.Throw{Msg: err.Error()} 253 } 254 return nil 255 }