android登录源码分析,Android 源码分析实战 - 授权时拦截 QQ 用户名和密码

1. 说在前面

本文内容其实是不适合发出来的,希望大家切勿用作商业用途,也切勿将功能发布到线上环境。技术一定是为生活服务的,是为了大家共同的美好生活。判断一件事是否值得做,一定是利人利己,错误的事可能舒服自己而痛苦了别人,损人利己的事千万不可为。

2. 需求背景

在测试功能时,我们可能会遇到一些偶现崩溃的情况,往往难以复现。在修改代码时,有时往往改了这个 Bug ,在某个其他地方又引发了另一个 Bug,或者又是不经意间修改了 UI 样式界面。当然以上问题,我们可以看日志跟踪,也可以多写单元测试。基于等等场景,我们部门共建了一个 sdk ,我也是其中的一员。

其中重要的一个环节,复现 bug 时需要还原当时现场。也就是说要还原当时测试同学的操作步骤,需要还原当时请求的网络数据,需要还原数据库,需要还原 SharedPreferences ,如果这些现场都能还原,就很有可能复现并解决这个奔溃。而这里还涉及到账号还原,也就是说要还原当时测试同学登录的账号,我这里说的是在另一台手机上自动切换登录。

同时基于以上功能,我们就可以事先录制很多正常操作路径,也可以把当时的每个界面布局录制下来。这样每次发布之前,都在云平台上跑一遍,就能测试发现很多问题。如何还原网络数据、还原数据库等现场本文暂时不讲,本文主要来分析如何还原账号信息。温馨提示,只要对源码足够熟悉,这些都不是事。

3. 需求分析

PCG 部门的所有产品都是比较成熟的产品,任何一个产品都有亿量级的用户,我们写的 sdk 是无法侵入业务代码的。也就是说业务开发的同学在 Application 中配置一个入口,以上这些功能就都要能实现。现在回到账号还原上来,如果要还原账号现场,那么必定会有账号拦截与自动切换登录,而这整个过程,业务上层是不会给我们适配代码的。因为腾讯视频、应用宝与腾讯新闻等等,整个 PCG 的应用都需要集成我们的 sdk。我们在写代码的时候一定要考虑通用性、适配性与集成成本等等。只是该功能只在测试环境集成。

PCG 应用业务侧登录都是用的 QQ 与微信第三方登录,我们规定测试同学只能用 QQ 授权登录,方便自动拦截切换登录实现。那么接下来第一步就是如何拦截登录的账号信息,我之前考虑过只拦截 QQ 授权时的信息,但后面发现授权信息的 token 会有过期时间,后面就果断放弃了。所以要实现该功能且要做到所有应用都通用,就只能想办法拦截到 QQ 授权时的用户名和密码。

4. 需求实现

怎么在不侵入业务逻辑代码情况下,拦截到 QQ 授权时的用户名和密码呢?估计大部分人都会认为无法实现,但其实只要对源码够熟悉,分析实现起来还是挺简单的。业务侧普通的授权方式是没有输入用户名和密码的过程,我们想要拦截这些信息,势必需要用户有这个主动的操作过程,基于这点我们就需要引导用户跳转到 H5 的授权界面。那这还不简单,我们在业务逻辑中直接打开 QQ 的 H5 授权不就可以了?但问题是我们不能改业务逻辑代码,而且该 sdk 也不会上线,因此我们只能在 Application 初始化 sdk 里面中去做处理。

那么怎么才能在不该动原有业务逻辑情况下,点击 QQ 授权是自动跳转到 H5 授权界面呢?我们势必要去翻一下 QQ 登录提供的 sdk 源码,发现其中会判断有没有安装 QQ 应用,如果没有则是提示下载 QQ ,如果有安装 QQ 则会跳转到 AgentActivity 进行授权。因此我们只要想办法欺骗 QQ 的授权 sdk 就行,当调方法问有没有安装 QQ 时,我们返回安装了;当启动 AgentActivity 授权时,我们偷偷的将其引导到 H5 授权界面去输入用户名和密码,只有这样我们才有机会拦截到用户名和密码。那么如何才能欺骗呢?这就取决于我们对源码的熟悉程度了。

// PMS

private HandlerInvokeCallback mPMSCallback = new HandlerInvokeCallback() {

@Override

public void beforeInvoke(Method method, Object[] args) {

try {

// 一般上层业务逻辑中会有判断 QQ 有没有安装

if (CommonUtils.equals("getPackageInfo", method.getName())) {

if (CommonUtils.equals(args[0], QQ_PACKAGE_NAME)) {

// 替换成当前应用的包名,无论是否安装 QQ 返回都是安装

args[0] = mApplication.getPackageName();

}

}

// SDK 中会查询 QQ_OAUTH_ACTIVITY ,默认返回 QQLoginH5AuthorizeActivity

if (CommonUtils.equals("queryIntentActivities", method.getName())) {

Intent queryIntent = (Intent) args[0];

ComponentName componentName = queryIntent.getComponent();

if (componentName == null) {

return;

}

String queryClassName = componentName.getClassName();

if (CommonUtils.equals(queryClassName, QQ_OAUTH_ACTIVITY)) {

args[0] = new Intent(mApplication, QQLoginH5AuthorizeActivity.class);

}

}

} catch (Exception e) {

e.printStackTrace();

LogUtil.w(TAG, "pms beforeInvoke exception: " + e.getMessage());

}

}

@Override

public Object afterInvoke(Method method, Object[] args, Object returnObj) {

return returnObj;

}

};

// AMS

private HandlerInvokeCallback mAMSCallback = new HandlerInvokeCallback() {

@Override

public void beforeInvoke(Method method, Object[] args) {

try {

// 把跳转到 QQ 原生的页面,都替换跳转到 QQLoginH5AuthorizeActivity

if (CommonUtils.equals("startActivity", method.getName())) {

Intent intent = (Intent) args[2];

ComponentName componentName = intent.getComponent();

if (componentName == null) {

return;

}

if (CommonUtils.equals(componentName.getClassName(), QQ_OAUTH_ACTIVITY)) {

intent.setComponent(new ComponentName(mApplication, QQLoginH5AuthorizeActivity.class));

}

}

} catch (Exception e) {

e.printStackTrace();

LogUtil.w(TAG, "ams beforeInvoke exception: " + e.getMessage());

}

}

@Override

public Object afterInvoke(Method method, Object[] args, Object returnObj) {

return returnObj;

}

};

// Application 中初始化入口

@Override

public void init(Application application) {

this.mApplication = application;

SharedPreferences accountSp = mApplication.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);

mAccountSpEditor = accountSp.edit();

// 读取之前存的 QQ 用户名和密码

mQQAccount = accountSp.getString(SP_QQ_ACCOUNT_KEY, "");

mQQPwd = accountSp.getString(SP_QQ_PWD_KEY, "");

mAppId = accountSp.getString(SP_QQ_APP_ID_KEY, "");

mAuthLoginType = accountSp.getInt(SP_QQ_AUTH_TYPE_KEY, 0);

// 手Q应用适配另一套

if (CommonUtils.equals(application.getPackageName(), QQ_PACKAGE_NAME)) {

adapterInterceptQQAccount();

} else {

hookPMSAndAMS();

}

LogUtil.i(TAG, "init read account info: " + mQQAccount + ", " + mQQPwd + ", " + mAppId + ", " + mAuthLoginType);

}

只要跳转到了 H5 的授权页面,拦截用户名和密码就很容易了。其实开发中我们看似很多实现不了的功能,只要我们静下心来去分析,还是能够实现的。这其实还是一个比较简单的功能,再扩展一些像还原网络现场,势必需要拦截监控用户的网络,而微视用的 wns 、手Q用的是 msf、腾讯体育是自己修改的 OkHttp 千奇百怪。

其实生活中总会有些事,不管我们有多少困难,有多少委屈,有多少艰苦,我们做下去,我们最终必然会感觉到骄傲。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值