牛骨文教育服务平台(让学习变的简单)
博文笔记

【集成】极验验证

创建时间:2017-06-28 投稿人: 浏览次数:1638

极验验证地址

http://www.geetest.com

极验验证用途

防止用户恶意频繁请求服务器,也就是防刷,主要用于安全认证。
看官方文档你会发现,其实就是通过弹出验证码窗口进行验证拦截,根据验证结果你可进行自己的操作。

极验验证的使用集成

此处在官方文档有说明,详情点击http://docs.geetest.com/install/client/android/,此处不做赘述。
在此主要说出集成极验验证时的一些坑,以及处理方案。
基本说明:需要服务器提供两个接口 初始化配置接口/验证接口
进入源码你会发现sdk通过

new GT3GeetestUrl().setCaptchaURL(captchaURL);
new GT3GeetestUrl().setValidateURL(validateURL);

将初始化接口/验证接口进行了保存(请自行查看源码),之后通过

gt3GeetestUtils.getGeetest();

进行开始验证,在此通过执行task来对之前配置的两个接口发起网络请求,那么问题来了
1.如何在请求中插入cookie

/**
     * 验证回调
     */
    private static class GTListener implements GT3GeetestUtils.GT3Listener {
        private Map<String, String> data = new HashMap<>();

        // dialog关闭
        @Override
        public void gt3CloseDialog() {
            isCloseFlag = true;
        }

        // 点击其他,关闭dialog
        @Override
        public void gt3CancelDialog() {
            isCloseFlag = true;
        }

        // 验证码加载准备完成
        @Override
        public void gt3DialogReady() {
            EvtLog.e("validate", "gt3DialogReady");
        }

        // 首次请求返回结果,将getCode拿到的两个数据,在发送验证的时候带回去
        // 因为sdk不支持添加cookie,所以用此方法来通知后台是否为同一用户
        @Override
        public void gt3FirstResult(JSONObject jsonObject) {
            try {
                data.put("captcha_uid", jsonObject.getString("captcha_uid"));
                data.put("status", jsonObject.getString("status"));
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        // 二次请求返回结果
        @Override
        public Map<String, String> gt3SecondResult() {
            return data;
        }

        @Override
        public void gt3GetDialogResult(String result) {

        }

        // 验证码 验证成功
        @Override
        public void gt3DialogSuccess() {
            isCloseFlag = true;
        }

        // 验证码 验证失败
        @Override
        public void gt3DialogOnError() {
            isCloseFlag = true;
        }

        @Override
        public void gt3DialogSuccessResult(String result) {

        }

        @Override
        public Map<String, String> captchaHeaders() {
            // 此方法只是将cookie相关数据加到header并没有加到cookie,依然会造成后台生成新的seesionid
            // 处理方案,后台返回一个唯一标识,在验证时返回 回去
//          LZCookieStore cookieStore = new LZCookieStore(FeizaoApp.mConctext);
//          List<Cookie> cookies = cookieStore.getCookies();
//          Map<String, String> data = new HashMap<>();
//          if (cookies != null) {
//              for (Cookie cookie :
//                      cookies) {
//                  data.put(cookie.getName(), cookie.getValue());
//              }
//          }
//          return data;
            return null;
        }
    }

在sdk提供的回调中captchaHeaders()是给 初始化配置接口请求时添加header,但是并未单独放入cookie下面,所以需要自己解析。
但实际应用场景一般会在cookie下面放入一些参数用于判断是否为同一用户下的请求,所以在这里有两个解决方案
1. 后台在此接口上做特殊判断,在header中直接去拿,我们通过captchaHeaders()将参数放入header;
2. 我们初始化请求时服务器返回一个参数作为唯一标识,在验证validate的接口时,将此参数值带入到请求body中。
在此段代码中,也就是我当前的项目中使用的是第二种方式进行的处理
2.如何避免谈起多个dialog
sdk通过geetestUtils.getGeetest();来开启验证的,那么我们看一下它的源码

public void getGeetest() {
        getLight = GT3LifecycleCallBacks.getInstance(context).gt3SendMsg();
        GT3LifecycleCallBacks.getInstance(context).clearAllMsg();
        senMsg = new GT3AroundMsg(context).gt3SendSenMsg();
        new GT3AroundMsg(context).endSensor();
        mGtAppDlgTask = new GtAppDlgTask();
        mGtAppDlgTask.execute();
        if (!((Activity) context).isFinishing()) {
            dialog = new GT3GtDialog(context);
            Window dialogWindow = dialog.getWindow();
            WindowManager.LayoutParams lp = dialogWindow.getAttributes();
            dialogWindow.setGravity(Gravity.CENTER);
            dialogWindow.setAttributes(lp);
            dialog.show();

            dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialo) {
                    gtListener.gt3CancelDialog();
                }
            });
            dialog.setGtListener(new GT3GtDialog.GtListener() {
                @Override
                public void gtResult(boolean success, String result) {
                    gtListener.gt3GetDialogResult(result);
                    if (success) {
                        mGtAppValidateTask = new GtAppValidateTask();
                        mGtAppValidateTask.execute(result);
                    } else {
                        //TODO 验证失败
                        dialog.shakeDialog();
                    }
                }

                @Override
                public void success() {
                    gtListener.gt3DialogSuccess();
                }


                @Override
                public void gtCallReady(Boolean status) {
                    if (status) {
                        //TODO 验证加载完成
                        gtListener.gt3DialogReady();
                    } else {
                        //TODO 验证加载超时,未准备完成
                    }
                }

                @Override
                public void gtCallClose() {
                    gtListener.gt3CloseDialog();

                }

                @Override
                public void gtError() {
                    gtListener.gt3DialogOnError();
                }

            });
        }
        captcha.setTimeout(5000);

        captcha.setGeetestListener(new GT3Geetest.GeetestListener() {
            @Override
            public void readContentTimeout() {
                mGtAppDlgTask.cancel(true);
                //TODO 获取验证参数超时
                //Looper.prepare() & Looper.loop(): 在当前线程并没有绑定Looper时返回为null, 可以与toastMsg()一同在正式版本移除
                Looper.prepare();
//                    toastMsg("read content time out");
                Looper.loop();
            }

            @Override
            public void submitPostDataTimeout() {
                mGtAppValidateTask.cancel(true);
                //TODO 提交二次验证超时
//                    toastMsg("submit error");

            }

            @Override
            public void receiveInvalidParameters() {
                //TODO 从API接收到无效的JSON参数
//                    toastMsg("Did recieve invalid parameters.");
            }
        });
    }

观察源码你会发现,
1. 只要当前activity未停止,只要你调用此方法就会打开新的dialog,并且sdk未提供dialog手动销毁的方法。所以在此你会遇到的问题是:当你请求多个接口并且都需要验证时,它会谈起多个对话框,影响用户体验。在此我做了一个处理,代码如下:

package packagename.common;

import android.app.Activity;

import com.efeizao.feizao.library.util.EvtLog;
import com.example.sdk.GT3GeetestUtils;

import org.json.JSONException;
import org.json.JSONObject;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by Elvis on 2017/6/21.
 * description : 使用极验验证请求,操作是否频繁,存在自动刷请求
 */

public class GTValidateRequest {
    private static GT3GeetestUtils geetestUtils = null;
    private static GTValidateRequest gtValidateRequest = null;
    private static boolean isCloseFlag = true;      //如果dialog被关闭则为true,否则为false

    public static GTValidateRequest getInstance() {
        if (gtValidateRequest == null) {
            gtValidateRequest = new GTValidateRequest();
        }
        return gtValidateRequest;
    }

    /**
     * @param context 上下文activity,用于第三方sdk使用(第三方仅支持activity作为上下文对象)
     */
    public void validate(WeakReference<Activity> context) {
        EvtLog.e("validate", "activity:" + context.get().getComponentName().getClassName());
        // dialog已经关闭
        if (isCloseFlag) {
            isCloseFlag = false;
            if (geetestUtils == null) {
                geetestUtils = new GT3GeetestUtils(context.get());
            }
            geetestUtils.setGtListener(new GTListener());
            geetestUtils.getGeetest();
        }
    }

    /**
     * 验证回调
     */
    private static class GTListener implements GT3GeetestUtils.GT3Listener {
        private Map<String, String> data = new HashMap<>();

        // dialog关闭
        @Override
        public void gt3CloseDialog() {
            isCloseFlag = true;
        }

        // 点击其他,关闭dialog
        @Override
        public void gt3CancelDialog() {
            isCloseFlag = true;
        }

        // 验证码加载准备完成
        @Override
        public void gt3DialogReady() {
            EvtLog.e("validate", "gt3DialogReady");
        }

        // 首次请求返回结果,将getCode拿到的两个数据,在发送验证的时候带回去
        // 因为sdk不支持添加cookie,所以用此方法来通知后台是否为同一用户
        @Override
        public void gt3FirstResult(JSONObject jsonObject) {
            try {
                data.put("captcha_uid", jsonObject.getString("captcha_uid"));
                data.put("status", jsonObject.getString("status"));
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        // 二次请求返回结果
        @Override
        public Map<String, String> gt3SecondResult() {
            return data;
        }

        @Override
        public void gt3GetDialogResult(String result) {

        }

        // 验证码 验证成功
        @Override
        public void gt3DialogSuccess() {
            isCloseFlag = true;
        }

        // 验证码 验证失败
        @Override
        public void gt3DialogOnError() {
            isCloseFlag = true;
        }

        @Override
        public void gt3DialogSuccessResult(String result) {

        }

        @Override
        public Map<String, String> captchaHeaders() {
            // 此方法只是将cookie相关数据加到header并没有加到cookie,依然会造成后台生成新的seesionid
            // 处理方案,后台返回一个唯一标识,在验证时返回 回去
//          LZCookieStore cookieStore = new LZCookieStore(FeizaoApp.mConctext);
//          List<Cookie> cookies = cookieStore.getCookies();
//          Map<String, String> data = new HashMap<>();
//          if (cookies != null) {
//              for (Cookie cookie :
//                      cookies) {
//                  data.put(cookie.getName(), cookie.getValue());
//              }
//          }
//          return data;
            return null;
        }
    }
}

在代码中我添加了一个boolean作为标识,用来判断dialog是否被关闭。
默认为关闭,也就是默认允许弹出dialog,当弹出后,状态设置为false,说明已经打开dialog;然后在它的监听中设置状态,gt3CloseDialog()/gt3CancelDialog()/gt3DialogSuccess()/gt3DialogOnError(),这几中情况dialog都会被关闭,我们在此将状态设置为true。这样就可以用来避免dialog的多次打开。(此方案之适用于之前dialog依赖的activity已经销毁。当activity销毁时,dialog也会销毁。如果存在未销毁情况,你只需要在此方案上加入activity是否为同一个的判断就好)。
3. 初始化GT3GeetestUtils
看源码(片段2)中你会发现,他在验证时使用上下文,将其强制转换为activity,所以这里,在初始化时必须传入的是 Activity,那么问题来了,很多时候我们不会直接将其放到activity去调用,更多的是写为一个工具类,在公共的比如网络请求的地方去调用,那么这个时候我们如何获取activity对象呢,在此我直列出代码,在全局保存一个activity来进行随时使用

/**
     * 获取栈顶activity
     * 通过注册activity的生命周期监听获取最新的栈顶activity
     */
     private static WeakReference<Activity> app_activity;
    private void initGlobeActivity() {
        getApplication().registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                app_activity = new WeakReference<>(activity);
                Log.e("onActivityCreated===", app_activity + "");
            }

            @Override
            public void onActivityDestroyed(Activity activity) {
            }

            /** Unused implementation **/
            @Override
            public void onActivityStarted(Activity activity) {
                app_activity = new WeakReference<>(activity);
            }

            @Override
            public void onActivityResumed(Activity activity) {
                app_activity = new WeakReference<>(activity);
            }

            @Override
            public void onActivityPaused(Activity activity) {
            }

            @Override
            public void onActivityStopped(Activity activity) {
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
        });
    }

在此我们就拿到了栈顶的activity,记得一定要用弱引用。

结束语

在此极验验证的集成问题已经介绍完了,如果有什么疑问可以博客留言或者发送邮件到elvis@guojiang.tv。
如果觉得可以帮到你那么就拉倒底部,赞一个。
实现方式已粘贴到博客,所以在此就不下发下载链接了。

声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。