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  }