Android开发规范

API接口规范

API接口安全设计规范

App的数据来源就是API接口,所以API接口对APP的重要性不言而语。设计API接口需要考虑的就是安全机制。

  • 防篡改
    防篡改就是防止请求的URL参数值发送至服务器的时候被改动。
    普通的API接口格式是xxx.html&key1=xx?key2=xx。我们采用sign签名的发送保证数据传输的正确性。
    App一般会在公司的后台申请一个appKey和appSecret,这两个是一一对应的。appKey会作为一个参数写在URL中,然后再发送至服务器。appSecret则用于参与生成sign的计算。一般采用安全散列算法实现,如SHA1.
    sign也要作为一个参数添加到URL中,和appKey一并发送至服务器,如xxx.html&key1=xx?key2=xx?appKey=xx?sign=xx。服务器接收到请求后,会通过appKey查找对应的appSecret,然后通过同样的散列算法,得到一个sign,最后对比一下两个sign是否相等。如果不相等则数据遭到篡改,废弃这条请求。
    (app发起http请求,对参数排序,然后使用参数与私钥拼接,在进行sha1加密等方式,生成一个签名出来,一起发给服务端,服务端这边获取到参数,签名,再使用自己的私钥进行同样方式的加密生成签名,比对签名是否一致。一致则认为合法,不一致则不合法。但是无法防止重复请求攻击)
    另外,关于appSecret有以下两种使用方式:1.appSecret直接写在客户端代码中,这样即可直接获取调用。2.appSecret还可以通过一个专门的getSign从后台获取。这种情况首先需要用户登录,登录成功后,服务器返回一个accessToken参数,然后调用appSecret接口时需要带上这个参数。

  • 防重放
    解决了数据被篡改的问题之后,还有一个问题就是,如果一条正常的请求数据被其他人获取到了,从而进行多次请求该怎么办?
    可以采用nonce+timestamp的解决方案。
    1.nonce
    nonce是一个随机数,有客户端生成,每次请求时将随机数作为一个参数发送给服务端。服务端会在数据库里查询是否有这个nonce,如果没有则时一条新的请求,进行正常处理即可;如果能查到已经存在这个nonce,则废弃这条请求。
    nonce可以通过UUID.randomUUID().toString()来生成。
    有个问题是,这个nonce在数据库中随着请求量的增大,产生的数据量也会越来越大。为了解决这个问题,我们可以采用timestamp时间戳的方式。
    2.timestamp
    时间戳是服务器给URL请求设定的一个有限时间范围起点。例如服务器认为客户端发送过来的timestamp与服务器当前的时间戳之差在10分钟值内,则认为这条请求是有效的。超过了10分钟则废弃这条请求。如果是10分钟内的请求,需要在数据库中查询nonce是否有记录,如果有记录,则废弃这条请求;如果没有记录,则记录这个nonce,并且将超过10分钟的nonce全部删除。
    关于这个timestamp获取的问题,同样需要从服务器获取,不然客户端怎么知道服务器的起点计算时间呢?上面说到有个获取appSecret的接口,其实我们可以在这个接口中一并获取timestamp。
    需要注意的是,客户端需要在每次发送URL请求的时候,计算一下timestamp的值。例如获取到的timestamp=1564588800,那么下次请求的timestamp的值是1564588800+diffTime。服务器收到timestamp会跟服务器当前的时间戳做对比,看是否大于10分钟。
    因此,一条正常的URL格式请求如下:
    demo.html?nonce=xx×tamp=xx&appKey=xx&sign=xx&key1=xx&key2=xx

  • HTTPS
    HTTPS是用SSL+HTTP构建的可用于网络传输以及身份认证的网络协议。HTTP使用明文通信,传输的内容可以通过抓包工具截取,HTTPS自动对数据进行加密压缩,防止监听,防止被抓包以看到明文,防止中间人截取。
    苹果从iOS9就开始默认使用HTTPS,Android9.0也开始强制使用HTTPS了,默认阻塞HTTP请求。

API接口通用设计规范

1.版本号
每一组API接口需要对应一个大版本号,大版本号一般是跟App的大版本对应的。例如App第一版本命名为v1,App第二版本经过改版后,接口返回的内容一般也会变化,这里命名为v2。

示例如下:
/api/v1/userinfo:表示v1这个大版本的App,有一个userinfo业务类型的接口。
/api/v2/userinfo:表示v2这个大版本的App,有一个userinfo业务类型的接口。
/api/v2.1/userinfo:表示v2这个大版本中,对userinfo业务接口进行了一些细微调整。

2.请求参数
请求参数由公共请求参数和业务请求参数组合而成。
1)公共请求参数

参数名 参数类型 是否必填 描述
nonce String 防重放攻击
timestamp String 防重放攻击
sign String 请求参数的签名
accessToken String 鉴权标志,用于登陆判断
appKey String 防篡改
version String 客户端版本号

其中version表示客户端版本号,此处将其传给服务器,由服务器根据客户端版本号进行一定的业务逻辑判断。
2)业务请求参数
以登录为例

参数名 参数类型 是否必填 描述
name String 姓名
password String 密码

一个完整的URL如下:
/api/v1/getUserInfo.do?nonce=xx×tamp=xx&appKey=xx&appKey=xx&name=xx&password=xx

3.返回值
使用JSON格式返回API请求的结果。JSON格式简洁,传输数据量小,而且能展示复杂的数据结构。

{
     
  "body":{
     }
  "code":0
  "msg":""
}
  • code:API接口执行状态,例如0表示成功,-100表示为例超时。
  • msg:非成功状态下需要说明的信息,一般与code状态码一一对应定义。
  • body:返回的具体数据,通常是JSON格式。

4.接口变更
一般来说,一个API接口投入使用后,除非这个接口确定废弃不再使用,不然一般情况下不能对这个接口进行修改。例如修改了API的请求参数和返回值,会对使用此API接口的App带来不可预估的影响,最严重的影响就是崩溃。

5.Restful API
Restful API:/api/v1/userinfo
Restful API通过URI(如api/v1/userinfo)来表示资源,通过GET、POST、PUT、DELETE等方法来表示操作行为。

  • GET:获取资源。
  • POST:新建资源。
  • PUT:更新资源。
  • DELETE:删除资源。

SDK设计规范

SDK通用规范

  • SDK发布时需要配套有完整且详细的使用说明文档,包括混淆配置说明。
  • SDK需要详细记录每个版本的变更内容。
  • SDK如果对外开放,需要有一个专门的网站,同时附上SDK的说明文档,demo,变更历史等。
  • SDK的minSdkVersion要尽量小。
  • 尽量不要引用第三方库,要尽量使用Android系统自带的功能,然后在其基础上进行封装。如果一定要用到第三方库,可以使用provided依赖,并告知调用方主动依赖这个第三方库。
  • SDK内部对于关键路径要有详细的Log记录,便于后期排查问题。
  • SDK对外提供的接口,需要对其传入的参数和合法性和有效性进行检测。
  • SDK需要有较强的容错性,要增大力度对SDK内部的异常进行捕获。
  • 如果打出来的包是AAR格式的,需要注意res下面的资源文件名称,以避免和调用方的res文件名称冲突,所以一般SDK里面的res文件名称需要加入特定的修饰符,例如公司+项目名称。

SDK开发规范

SDK需要做到代码结构层次分明,功能清晰。
一个典型的SDK一般可以分为以下3层。
1.接入层
接入层的功能就是对外提供接口,供给调用方使用。一般对外提供的接口我们会定义一个接口文件,里面的方法都是可以对外提供的接口。所以接入层一般都是定义的接口文件,另外还有一个统一管理所有业务功能模块的雷,是外界跟sdk交互的统一入口。它还负责统一配置和进行初始化工作,例如初始化业务模块的Manager文件。

2.业务层
业务层的作用就是实现具体的业务逻辑。对于业务层的设计,我们会按照业务功能划分为不同的模块,每一个模块通过对应的Manager文件进行管理,并且Manager文件会具体实现接入层定义的接口方法。
3.基础层
基础层里面包括各种功能模块,例如SDK自行封装的网络请求模块,还有数据库模块、日志模块、Crash模块等。总而言之就是对业务层的支持。

App常用安全开发规范

组件外露

Android的四大组件Activity、Service、ContentProvider、BroadcastReceiver,有一个android:exported属性。如果是false,那么只能在同一个应用程序组件间或带有相同用户id的应用程序间才能启动或绑定该服务;如果是true,则该组件可以被任意应用启动或执行,这样就会有组件被恶意调用的风险。
如果组件没有包含过滤器intent-filter,那么android:exported属性的值默认是false;如果组件中包含了至少一个intent-filter,那么android:exported属性的值默认是true。
如果必须暴露这些组件,那么需要添加自定义的permission权限来进行访问控制。

<activity 
  android:exported="true"
  android:name=".DemoActivity"
  android:permission="com.androidwind.permission.demoPermission"
/>

外部应用如果想直接打开DemoActivity,需要在AndroidManifest中进行配置:

<uses-permission android:permission="com.androidwind.permission.demoPermission"/>

WebView

因为webview在低系统版本中存在远程代码执行漏洞,如JavaScriptInterface,中间人可以利用此漏洞执行任意代码,所以app的targetSdkVersion需要大于17,另外需要将webview自动保存密码的功能关闭:

webview.getSettings().setSavePassword(false)

so文件

通常简单的做法是将密钥等敏感信息保存在java代码中,例如直接写在静态变量里,但是这样很容易被编译破解,即使代码有混淆。
可以考虑将一些敏感信息,通过cpp代码保存在so文件中。这样会增加敏感信息被破解的难度。
需要说明的是,如果使用so文件,那么这个so文件也需要加壳。
github上有一个可以自动生成加密so库的插件cipher.so,这样通过在gradle里面配置需要加密的数据,即可加密保存到so库,并且自动生成对应的java接口。

你可能感兴趣的:(android,java)