Android逆向入门3——怎样才能找到sign算法

在上一节中,我们学习和安装了不少反编译App的工具,这一节,我们正式开始探寻Sign参数的生成。

资源:小红书6.3.0版本,在第一篇逆向教程的百度云链接中有,也可以去百度上搜索豌豆荚,在里面可以查看并下载历史版本6.3.0。

思路分析和选择

已知一个URL,想知道里面某个参数的生成,那怎么定位到这个字段生成的地方?

接下来我们循序渐进学习和使用这些方法,这样做可能会比较耗费时间,但优点是学习曲线不会太陡峭,而且不容易一知半解。平缓、详尽,循序渐进是我这个系列教程努力在做的事。

打开Jadx,将小红书拖进去,如果你没有遵循了(2)中的建议,将设置调成自动反编译的话,你需要在界面左上角点击红框中任意一个按钮,这两个都是jadx的字段搜索框,当检测到Apk还未反编译时,就会进行反编译。
在这里插入图片描述
搜索关键信息是定位信息生成处最通用和常规的方法,我们看一下这条GET请求的完整信息。
在这里插入图片描述

尝试一:搜索字段名

首选肯定是sign本身,我们在window中可以使用快捷键Ctrl+N全局搜索,mac类似,可以查看一下。
在这里插入图片描述
搜索类和文本的功能是重叠的,选择一个简单的快捷键即可。
在这里插入图片描述
勾选忽略大小写的原因:原文中可能会是Sign,SIGN。
搜索加“”的原因:开发过程中,字段一般是在Java集合或者json中进行操作,往往是xxx.put(“name”, value),所以双引号可以缩小查找范围。

我们得到了好几十个检索结果,接下来怎么办呢。
先不用任何技巧,我们从最上面开始看,双击第一条内容的"代码"列字段。
在这里插入图片描述

黄线标出了我们的sign,和预期符合,JSONObject.put(“sign”, value)
后面那一串就代表了sign的具体值,再仔细看一下它,getsign(xxxxxx),我的老天鹅呀,多么可爱直白的函数名,这一定就是sign的生成函数了,我们可以按住Ctrl,点击getsign,可以进入这个函数看一下。
在这里插入图片描述
先构建了一个空的String,然后通过append()把咱们传来的几个值,下面有几个很明显的MD5,这显然是MD5加密的方法,而咱们的SIGN也确实是32位的16进制字符串,老天鹅啊,真是太吻合了。那PRIVATE_CODE是什么东西,同样按住Ctrl,点击PRIVATE_CODE。
在这里插入图片描述
毫无疑问,这就是salt,也就是我们俗称的加盐,单纯使用内容拼接后加密不太安全,可能会被人猜出来之后拼接出来,但是加上这么一串东西,就万无一失了。

好了,SIGN值搞定。

停停停停停停停停停停停停停停停停停

错了错了,这儿几乎不可能是我们需要的Sign加密处。
在这里插入图片描述
为什么呢?——因为这个加密方法所属的类一点也不对。
我们先退回到起初的地方,下图这两个箭头可以实现前进和倒退。
在这里插入图片描述
在这里插入图片描述
可以看到搜索界面的排版设计如下

节点 代码
com.sina.weibo.sdk.statistic.LogReport jSONObject.put(“sign”, getSign(jSONObject.getString(“aid”),……
xxxxxxx xxxxxxx

代码列展示了关键词前后的相关内容,我们刚才也是通过双击它进入了代码详情页。
那节点是什么意思呢,节点代表了这段代码所在的方法,Python中更爱称为函数。
com.sina.weibo.sdk.statistic.LogReport.requestHttpExecute
开头标识度很高,com.sina.weibo.sdk.xxxxxx

SDK即软件开发工具包,新浪的sdk用来干啥呢?
它是对新浪微博公开接口的一些封装,应用的开发者可以使用它来访问新浪微博的 API,进行登录授权,获取用户信息,获取微博列表,发微博等等。

观察一下小红书的登录界面,显然它这儿是在做第三方登录和授权。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190723134753397.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4ODUxNTM2,size_16,color_FFFFFF,t_70 ==500x300)
我们还可以搜索一下微信和QQ SDK的一些特征,会发现大量的结果,这些都是授权登录的代码,我们不用去管它。

  • com.tencent.tauth.AuthActivity QQ
  • com.tencent.mm.opensdk 微信
  • com.sina.weibo.sdk 微博

好了,第一条被排除了,我们可不可以用类似的方法排除一些呢?——————当然可以。
在这里插入图片描述
在这里插入图片描述
最后排查出来只剩下十来个了。
一个一个去试试,然后里面的某一条可能是你需要的,也可能在分析后发现它不是。
在这里插入图片描述
这真让人绝望,玄学的意味太大了,我们得想办法让玄学的意味不那么重。

我们尝试一下其他关键词
这条GET请求有十来个字段,我们搜索哪一个呢?
一般来说,我们搜索更能代表这条请求的字段,或者退而求其次——App中出现次数尽可能少的字段,以免你要排查成百上万个信息点。

我们先试试更能代表这条请求的字段,我们这条请求是这样发出去的,搜索框搜索欧阳娜娜,发出去第一条GET请求,下拉,不断出现这个请求。
在这里插入图片描述
在这里插入图片描述
参照小红书的具体逻辑,Keyword和filters(过滤)一定是最能代表这个逻辑和请求的字段,这条请求的sign一定是在它周围,或者在它之后的逻辑中出现。

在这里插入图片描述
filters有28个结果,代码的形式也可能让一些人很困若。@l(a=xxxx ……或者q xxx(@Query)……
怎么办呢???
换成keyword,也光速打脸,竟然有一百多个结果,需要翻页才能看到底。
在这里插入图片描述
如果你是一个熟练Android开发或者熟悉前端架构,在上面的某几步里你会很轻易的找到线索。
可是你不熟悉,那怎么办呢?
在这里插入图片描述
我们换一种方法试试

尝试二:搜索URL

我们这里找一张网上的示意图进行说明
在这里插入图片描述
为了复用性,很多时候App会将BASE_URL,也就是域名和后面的虚拟目录/请求参数拆分开来。
所以我们尝试搜索虚拟目录的末端这块儿
在这里插入图片描述
在这里插入图片描述

nice!只有七个结果,而且明显只有第一条,第五条符合我们的要求,其余的结果,url后面的路径明显和我们的路径不同,第二条是v8,我们需要的是v9,不要看走眼了。

我们点开看一下这两处的内容

@GET("/api/sns/v9/search/notes")
        public static /* synthetic */ q searchSnsNoteBeta$default(AliothServices aliothServices, String str, String str2, String str3, int i, int i2, String str4, String str5, String str6, int i3, int i4, int i5, Object obj) {
            if (obj == null) {
                return aliothServices.searchSnsNoteBeta(str, str2, str3, i, i2, str4, str5, str6, i3, (i5 & 512) != 0 ? 1 : i4);
            }
            throw new UnsupportedOperationException("Super calls with default arguments not supported in this target, function: searchSnsNoteBeta");
        }
@GET("/api/sns/v9/search/notes")
    q<SearchResultNotesBean> searchSnsNoteBeta(@Query("keyword") String str, @Query("filters") String str2, @Query("sort") String str3, @Query("page") int i, @Query("page_size") int i2, @Query("source") String str4, @Query("search_id") String str5, @Query("api_extra") String str6, @Query("page_pos") int i3, @Query("allow_rewrite") int i4);


可以看到,第一处代码的返回结果,当obj参数为空时,返回的结果中使用的是二的函数(我们这里先叫它函数),否则抛出异常。所以归根结底还是在第二处代码这儿。
这几行代码究竟在干啥呢???全局搜索URL的最终结果就是这儿,它到底是什么呢?我们怎么找SIGN的加密呢??

这个时候我们需要使用Jadx的另外一个功能——查找用例
查找这个函数被用在了哪儿,鼠标悬停在这个函数名上,右键,查找用例。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
KEY_SPLASH_SORT是什么呢,我们悬浮在单词上面,Ctrl左键进去。
在这里插入图片描述
你会发现一个很困惑也很惊喜的事情
在这里插入图片描述
在这个方法里面,出现了这六个字段的name。k.b是什么方法呢?我们进去看一下。
在这里插入图片描述
当第一个参数出错时,下面Thread.currentThread.getStackTrace巴拉巴拉,这在任何一门语言中都是相似的。翻译一下就是:如果第一个参数为空,那么就报错,报错内容为Parameter specified as non-null is null(不允许为空的值结果为空):method:类名.方法名,parameter(参数名):str(我们传进来的第二个参数)。

到这里应该很清楚了,这个类给了我们很大的信心。
q <SearchResultNotesBean> a(String str, String str2, String str3, Integer num, Integer num2, String str4, String str5, String str6, int i, int i2)

这里面传进来是个参数,str,str2等等六个字段的意思我们已经知道了。那另外四个呢?会不会就是我们需要的sign,或者其他字段呢?

——————————————————————————————————————————————
打住,到此为止吧,让我们反思一下,这样做下去真的能得到结果吗?没有正向开发的知识,稍微摸一些Java代码,真的能每次都运气好撞到结果吗?
显然是无稽之谈,接下来我们具体探讨一下Android app中的网络通信,以及小红书App,它到底是怎么发出这条请求?

首先我们需要了解一下App开发的框架
你也可以简单的划分为原生App、WebApp,其他App。
具体的细节看这篇文章:https://www.cnblogs.com/windfic/p/10443342.html
原生App仍然是主流,App的语言特征为Android中由C、Java、Kotlin开发,ios中由C、Objective-C和Swift语言开发。
WebApp的出现主要是为了满足跨平台的需求,WebApp主要由由Javascript构建。
在这里插入图片描述
不同的架构使用了不同的语言,自然也使用了不同的网络通信框架,有一个简单的App可以辅助我们识别App的架构,我放在了百度云里,当然,也可以去原作者的链接下载。
包含本节全部内容的百度云资源
链接:https://pan.baidu.com/s/13i55E144WcE_BJmLA5VIrg
提取码:gbzz

原作者链接:https://www.v2ex.com/t/461934

安装并打开这个App,选择小红书。

在这里插入图片描述

在这里插入图片描述

结果显示是ReactNative+Kotlin语言开发。前者是可以用于构建用户界面的 JavaScript 库,也就是App的界面,后者是Android的开发语言。这是不是就是说小红书就没有用Java代码呢?显然不是的,这个架构检查的App只是检测了包含了的语言和技术,我们可以看一下它的源码。
在这里插入图片描述
我翻译一下,每一种框架都必然有很多依存的文件和数据,它检测了App中是否有这些文件,最终返回一个数组,包含了App中检测到的技术。

小红书的检测结果就是[ReactNative, Kotlin]
我们该怎么使用这个工具呢?我建议是先当成原生App分析,毕竟原生的框架才是主流,如果发现代码差异很大,或者说找不到我们在下文中讲到的原生App中的这些库,再用这个软件检测,看是不是它用了别的框架实现了这个功能。

所以,接下里我们开始介绍Android网络通信

Android网络通信浅析

我们做爬虫的在搞App逆向时,大部分时候其实就是在和App中的网络通信打交道。
我们先聊一下Android的网络通信可能从什么地儿发出,以Python类比,Python原生的网络请求库是urllib和urllib2,它们可以很方便的实现http服务的调用,而Python的第三方库Requests库是基于 urllib的封装,它更加方便和优雅。

我们按照远近亲疏,缕一缕Android中的网络通信框架。

1. Android原生自带的网络通信库

  • HttpURLConnection
    标准的Java接口(java.NET),HttpURLConnection基于http协议,支持get,post,put,delete等各种请求方式。。
  • HttpClient
    Apache的HttpClient模块(org.apache.http),这个模块被放在Android的SDK(软件开发工具包)中,它旨在提供高效的、最新的、功能丰富的HTTP 服务。

这里需要讲一下这两大通信库的前世今生,在Android 2(2010年)的远古版本中,HttpURLConnection有个重大 Bug,所以那个时候推 荐使用HttpClient。时光荏苒,BUG被修复了,HttpURLConnection也不断得到优化和完善。从Android 5(2014年)开始,Android官方不再推荐使用Apache模块, Android 6.0的SDK中去掉了HttpCien,到了最新的Android 9版本中,Android更是彻底取消了对Apache HTTPClient的支持。

Java开发可以使用HttpClient,Android开发官方推荐用HttpUrlConnection。

2. 封装好的网络通信工具/框架

在Android实际开发中,一般都会使用别人封装好的第三方网络请求框架,原因也很简单,网络操作涉及到异步以及多线程,自己动手实现的话很麻烦。

  • Volley
    在2013年的Google I/O大会上,Google推出了这款异步网络请求框架和图片加载框架。它特别适合数据量小,通信频繁的网络操作。它基于HttpUrlConnection,目前也有一定的使用量。
  • Android-Async-Http
    基于Apache HttpClient库之上的一个异步网络请求处理库,现在已经不怎么用了。一是因为HttpClient被Android弃用,二是框架作者已停止维护,这个库知道即可。
  • OkHttp
    OkHttp是大名鼎鼎的Square公司的开源网络请求框架,需要注意的一点是,它并非是基于HttpURLConnection和HttpClient的封装或者补充,事实上,它们是平级的,三者构成竞争关系。从Android 4.4开始,HttpURLConnection的底层实现也已经基于OkHttp,由此可见OkHttp是时下当之无愧最热门的HTTP框架。OKhttp简单、快速、高效,在底层实现上自成一派,是基于Socket的封装,后续实践中我们会体验到它的这些特点。

还没完,我们接下来还得介绍一个重头戏——Retrofit

  • Retrofit

同样出自于Square公司,Retrofit对Okhttp做了一层封装。但你千万别小瞧了这层封装,如果是Java老手或者Android开发人员当我没说,如果是新手,从这儿开始,就不能当成听故事了,得好好理解。

先看一下比较简单的介绍

Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装,网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装。

现在App中最流行最常见的网络请求框架就是Retrofit+Okhttp,小红书也不例外。

接下来我们照葫芦画瓢写一个demo App,用Okhttp/Retrofit发送一个请求,你就会对App的网络通信有一些了解。

首先创建一个简单的App
打开Android studio,新建项目,选择Empty Activity
在这里插入图片描述

在这里插入图片描述
等待其构建完毕
在这里插入图片描述
在这里插入图片描述

我们做一个最简易的demo,只有一个界面,一个按钮,每次点击请求,它就会访问一个api,得到其数据并返回。
先看一下我们想访问的URL:https://www.meituan.com/meishi/api/poi/getMerchantComment?platform=1&partner=126&originUrl=https%3A%2F%2Fwww.meituan.com%2Fmeishi%2F6309410%2F&riskLevel=1&optimusCode=1&id=6309410&userId=&offset=0&pageSize=10&sortType=1
美团的域名,GET请求,十个参数,这也比较符合我们真实的网络请求。

用浏览器访问一下,看看返回数据,很标准的Json格式,一切都没问题。
在这里插入图片描述

使用Postman或者Python模拟请求,会发现必须要有user-agent才可以成功访问,那我们再先搞个user-agent。

"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"

接下来我们先用Okhttp库发送这个网络请求。

先进行配置,这一块你可能会对一些名词感到困惑,或者你可能一点也不懂Android开发,那么可以买一本《第一行代码》,这本书可以让你对Android开发有最基础的了解。除此之外,我更建议你遇到一个不理解的名词,就直接百度/Google搜索它。最好选择2019年发布的知识和见解,因为知识和技术更新太快了。关于Android开发,Google亲儿子Android Studio已经更新到3.5了,直到如今网络上还有很多Eclipse的资料和内容,所以一定要甄别清楚。

我们开始配置
首先是导入所需要的类库,在Python中,我们会用import requests导入Requests库,我们在Android开发中,也要做类似的事。

先选择目录展示方式,我们用的比较多的是Project和Android这两种。Project代表了项目在本机中的真实目录结构,后者是一种更加方便Android快速开发的目录结构。我们先用Android目录结构。
在这里插入图片描述
在这里插入图片描述
我们需要在App的build.gradle中添加对Okhttp库的依赖。

implementation 'com.squareup.okhttp3:okhttp:3.4.1'

在这里插入图片描述
库已经添加好了,接下来我们需要获取一下网络权限。
为什么要获取网络权限???我小猫咪写Python爬虫时可从来没人让我这么卑微,联网都要汇报一下???

这其实是一种很合理和安全的设计,作为一个开发者,你的App可能要进入千家万户的手机,那么就需要声明和细化你需要的每一个权限和隐私,这样对用户和App自身都好。

我们再了解一下Activity是什么,Android有四大组件,Activity是直接和用户交互的界面,类似于网站中展示给用户看到页面。
建议浏览一下这两篇文章

  1. AndroidManifest.xml详解
    https://www.jianshu.com/p/3b5b89d4e154
  2. Android——四大组件、六大布局、五大存储
    https://www.cnblogs.com/DreamRecorder/p/8949384.html

四大组件,以及各种权限,都必须在Manifest.xml 清单文件上声明一下,也称为“注册”。

INTERNET 也就是大写的internet,把这一句如图进行添加。
在这里插入图片描述
导入库,声明权限,同步代码,接下来我们就可以开始编写OKhttp发送请求的具体代码了。

Android程序的设计讲究逻辑和视图分离,我们先来写视图。

在这里插入图片描述
这个xml默认使用的页面布局方式为约束布局,而且已经默认有了一些内容,在屏幕中心打印"Hello World!"字样。
我们先将不需要的内容全部清空,并将布局方式改为最简单的线性布局。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

视图界面搞定了,接下来我们要在MainActivity中写我们的业务逻辑。
在这里插入图片描述
我们先要为OkhttpButton按钮增加一个事件监听器,这样每次点击这个按钮,就会有对应的代码去处理它。

你的代码会不断飘红,只要按照它的提示,用快捷键导入对应类和库即可。
在这里插入图片描述
这是我们写下的第一行逻辑代码为了更容易看清,我选中了注释,截张图。
在这里插入图片描述
接下来另起一行,这儿不用傻傻的一个一个字母敲,注册监听器时,我们在括号里用匿名内部类的形式创建了onClick()方法,只要将待处理的逻辑写在这个方法里,每次点击okHttpButton就会触发。
在这里插入图片描述

在这里插入图片描述
到目前为止,Activity中的全部代码
在这里插入图片描述
接下来按照图示进行代码编写,如果你的OkHttpClient始终飘红,使用Alt+Enter也无法在列表中找到导入Okhttp库的选项,可以在最上方手动进行导入.

import okhttp3.OkHttpClient;

下面我提供MarkDown代码的完整注释和代码,当然,你也可以直接使用我提供的百度云链接下载今天的全部资源和代码。
在异步网络操作的过程中,可以使用快捷导入,我先截图演示一下,防止有人傻傻的一行行敲代码浪费时间。
除此之外,网络请求这种操作,是强制要求用try catch进行异常处理的,在代码复写的过程中,你可以直接写try catch里面的代码,然后你的代码会飘红,Alt+Enter即可自动生成Try catch。
在这里插入图片描述
完整的代码

package com.example.testretrofit;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;


public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button okHttpButton = (Button) findViewById(R.id.OkhttpButton);
        /* 先看右边,findViewByID方法你一定不陌生,它和网页中类似,在这里我们通过ID找到了我们先前在
        视图中定义的OkhttpButton按钮,(Button)是在做强制转换,因为被"findViewByID"找到的控件并不知道
        自己是什么类型,就像小龙虾也不知道自己是干垃圾还是湿垃圾一样,你要具体指定类型。
        再看左边,我们声明了一个Button类型的变量,取名为okHttpButton,然后把右边赋给左边。
        从此,okHttpButton就代表了一个你在视图中创建的那个OkhttpButton按钮。
        */

        // 接下来为okHttpButton注册一个监听器
        okHttpButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String requestUrl = "https://www.meituan.com/meishi/api/poi/getMerchantComment?platform=1&partner=126&originUrl=https%3A%2F%2Fwww.meituan.com%2Fmeishi%2F6309410%2F&riskLevel=1&optimusCode=1&id=6309410&userId=&offset=0&pageSize=10&sortType=1";
                //获取OkHttpClient对象
                OkHttpClient mOkHttpClient = new OkHttpClient();
                // 构造Requests对象
                Request.Builder builder = new Request.Builder();
                // 下面是链式调用的写法,可以让我们的代码更加简洁易懂
                Request request = builder.get()
                        .url(requestUrl)
                        .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36")
                        .build();

                //将Request封装为Call
                Call mCall = mOkHttpClient.newCall(request);
                //开始执行Call
                // 下面就是发送同步请求的方法,但是开发过程中,一般都用异步回调的方式进行网络通信
                // 为什么要异步呢?因为网络操作的响应时间是不定的,万一网络波动,在同步的情况下,
                // 你的界面就得卡好几秒来等网络请求的返回结果,这显然十分愚蠢。所以我们使用异步方法。
                //Response mResponse= mCall.execute();//同步方法  ———— 我们这儿不做演示

                mCall.enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {
                        Log.e("testMyApp", "出错啦!");
                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        String result = response.body().string();
                        Log.d("testMyApp", result);
                    }
                });



            }
        });


    }
    
}

代码中的Log.d可能会让你困惑,比如这条Log.d(“tag”, “message”)。
其实这是Android 的日志机制,d代表了Debug调试,第一个需要传入的参数是自定义的标签,它代表了从哪儿发出,或者说什么地方出了问题,利用它我们可以在日志中进行信息的筛选和定位,比如我们的Tag就是testMyApp,第二个参数的具体的输出内容。
具体的Log用法可以参考这篇文章: https://www.cnblogs.com/andy-songwei/p/9676823.html

接下来我们要运行这个App了,有几种方式,一是usb连接真机,或者打开模拟器,如果顺利的话,Android Studio右上方会显示出你的手机型号,直接点击Run按钮即可。
在这里插入图片描述
如果没有显示手机型号,可以如下图在Terminal中查看手机设备连接情况,如果没有显示设备,重启模拟器/重连手机。
在这里插入图片描述

或者,你也可以下载Android Studio自带的模拟器,百度搜索Android Studio运行即可,可以参考这个链接https://blog.csdn.net/qq_41916089/article/details/81044989。

假设一切就绪,点击Run后,连接的模拟器/手机就启动了我们编写的程序。
在这里插入图片描述

我们可以使用Android Studio的Logcat功能,查看我们写的Log。
在这里插入图片描述
怎么在茫茫日志中找到自己的那几条呢?搜索一些即可,搜索“testMyApp”,然后点击App中我们设置的OkHttp按钮。
在这里插入图片描述
这正是我们需要的结果。我们通过Okhttp发送了异步请求,并编写了回调函数,如果请求顺利,就打印Response body的结果,如果失败,就打印“失败啦”。截图中的第三条日志就是我在断网状况下请求的结果。

不知道你有没有发现,在学习了使用Okhttp发送Get请求后,你对小红书的网络通信依然毫无了解。你还是不清楚你减速出来那么多乱七八糟的@注解和xxxservice是干嘛用的,这是因为现在主流的原生App网络框架是Retrofit+Okhttp,Retrofit是对Okhttp的封装,在App中,主要还是Retrofit的代码。

下一讲接我们使用Retrofit做同样的事,你会发现一切豁然开朗。

7/27 内容修正

修正一个错误
在文章中段的时候,有这样一段内容

使用Postman或者Python模拟请求,会发现必须要有user-agent才可以成功访问,那我们再先搞个user-agent。
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"

在之后Okhttp请求构造时,以及下一篇的Retrofit请求构造时,我们都加了header,这其实是多此一举。你可以测试一下,将.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36")删除,并不会影响结果的正常返回吗。
为什么呢?首先,这个网址确实是需要User-agent才能正常访问,不论是在Python中用Requests库或者在Java中用HttpUrlConnection,大家可是试验一下。而使用Okhttp或者Retrofit却不必加User-agent,因为Okhttp会默认给你加一个User-agent。我们用抓包软件看一下。
在这里插入图片描述
这是OKhttp默认给我们添加的一些Header字段。
那我们可能会想知道

  1. 那我想自定义添加User-agent或者别的字段,怎么办?它会不会把我的覆盖掉?
  2. 这个是怎么实现的?

那我们就要分析一下Okhttp的源码,你可以直接看这篇文章:https://blog.csdn.net/chunqiuwei/article/details/71939952 ,也可以听我直接叨叨一下结论。

在这里插入图片描述
在Okhttp请求发出前,会通过BridgeInterceptor这个拦截器检查一下Okhttp的Headers,如果用户自定义了User-Agent,就不管,直接放行,没有的话就添加格式为Okhttp+版本号的User-Agent,源码非常清楚了。
我们可以测试一下,故意写错User-Agent,看一下抓包结果。
在这里插入图片描述
这个问题就算解决了,下一篇中的Retrofit也不需要加header,因为Retrofit只是对Okhttp进行了华丽优雅的封装,其实现还是基于Okhttp。

发布了27 篇原创文章 · 获赞 120 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_38851536/article/details/96736225