需求
刚接到这个需求时,心里一惊。由于这一块,算是归于常识的盲区。不过也正好趁着这个机会,学一下关于接口加密相关的常识。
AES加解密
加密办法分好多种,由于后端同学和ios端已经接入了AES加密,所以这儿首要介绍AES加密。
关于AES加密,重点还是密钥,客户端和后台根据对应的密钥进行加解密。
而接口加密,并不是加密整个接口。
www.baidu.com/search?=android
例如这个接口,假如需要对其进行加密的话,一般状况下都是对”search?=android”进行加密。加密的办法一般是由后台和前端一致商量(大部分还都是后端做决定的)。
加密逻辑
网络恳求大略分两种,一种是GET
,另一种是POST
。注:当然还有其他的的恳求,如PUT、DELETE,但大多数开发状况下,只会用到这两种办法。
GET
恳求是将参数query拼接在url后边,而POST
则是将参数存放在requestBody中。所以这两种办法加密逻辑是不一样的。
GET
按着后端同学提供出来的文档能够知道,GET
恳求分两种状况,假如参数为空,则不需要加密,直接恳求;假如参数不为空,则将一切参数加密,将加密后的字符串拼接在域名后边,
例如,
原链接为www.baidu.com/search?=android
那么,加密后格局为www.baidu.com/url?key=abcdefg
其中”abcdefg“为加密后的字符串
POST
由于post恳求一切参数都在body里,所以只要对整个body进行加密,然后body传递加密后的字符串就行了。
Response
一切响应接口回来的数据都进行Base64编码及Aes解密。
其他的逻辑
这儿提到的加密逻辑,只是和后端协商后得到的,当然还有其他的加密办法,像能够在接口处拼接时刻戳,将token进行MD5,再拼接在url上,或许其他的加密办法(RSA等办法)。
代码
直接在拦截器一致处理GET
和POST
恳求。
1、判别办法
2、假如是GET,则获取一切的query,不然直接第5步
3、将获取的参数转为json格局并加密
4、拼接
5、假如是POST,获取body数据
6、将body转为json格局字符串并加密
7、将字符串作为恳求体post恳求
8、回来response的data数据解密
1、在拦截器内,经过val originalRequest = chain.request()
办法获取恳求链接,再经过 originalRequest.method()
办法获取恳求的办法。
2、经过val url = originalRequest.url()
获取恳求的url链接,再经过 url.queryParameterNames()
获取到此次GET恳求的一切参数名,经过url.queryParameter(QueryName)
获取参数名对应的参数值。
3、将一切参数转换为json格局,然后经过密钥进行加密操作。
4、拼接。这儿用密钥对json字符串进行aes加密,然后对链接进行拼接。能够经过url.encodePath()
获取途径。
5、POST恳求同理
6、将response的body的data数据解密
7、将response的body替换,并回来response
问题
1、POST恳求的body办法不能直接获取。
2、有一点疑问的是,什么叫将body加密,再把加密后的字符串作为body传值。
3、response并不能直接设置body。
处理
1、这个容易处理,网上有很多方案。
2、一般POST恳求,会经过
data.toJson().toRequestBody(“application/json;charset=UTF-8”.toMediaTypeOrNull())
或许
RequestBody。create(MediaType.parse(“application/json;charset=utf-8”), data.toJson())
这两种办法,都将数据转为body,而经过POSTMAN能够看到此时body是一串json字符串
{”name“:"xiaoming", age: 13}
。
那,怎样讲json转化,然后将加密后的内容重新作为body传值呢?
或许聪明的我们一下子想到了,但其时coding的时分,我脑子一时刻没转过来。
既然data
能够toRequestBody转换成body的值,那么,加密串呢?
AesUtil.decrypt(data).toRequestBody(“application/json;charset=UTF-8”.toMediaTypeOrNull())
发现这样也是行的,body里能够传任何数据,但它有必要是RequestBody格局。
3、本来的val response = chain.proceed(originalRequest)
中的response是不能设置body的,那么,解密后的数据只能用新建的response。
val newResponse = okhttp3.Response.Builder()
将老的response的code、message赋值给newResponse,设置body,当然还不要忘记了设置newRepsonse的request和protocol,不然后报错。
相关代码
以下是拦截器内的代码
var originalRequest = chain.request()
val builder = originalRequest.newBuilder()
builder.header("token", token)
val url = originalRequest.url()
val path = url.encodedPath()
val query = url.encodedQuery()
val method = originalRequest.method()
when (method) {
"GET" -> {
if (!query.isNullOrEmpty()) {
//恳求参数不为空:GET恳求接口加密为全参数加密,详细格局为 url?key=加密串
val queryList = url.queryParameterNames()
val map = HashMap<String, String>()
val iterator = queryList.iterator()
for (i in queryList.indices) {
val queryName: String = iterator.next()
map[queryName] = url.queryParameter(queryName) ?: ""
}
val s = gson.toJson(map)
val newBody = AesUtil.encrypt(s, KEY)
val newQuery = "?key=${URLEncoder.encode(newBody)}"
val newUrl = BASE_URL + path + newQuery
builder.url(newUrl)
runOnUiThread {
tvNew?.text = newUrl
}
} else {
//恳求参数为空:不需要进行加密,直接恳求
}
}
"POST" -> {
val body = originalRequest.body()
//暂不考虑formBody的状况,由于表单格局提交的post恳求,不需要加密
val buffer = okio.Buffer()
body?.writeTo(buffer)
var charset = Charset.forName("utf-8")
val contentType = body?.contentType()
if (contentType != null) {
charset = contentType.charset(Charset.forName("utf-8"))
}
val data = buffer.readString(charset)
runOnUiThread {
tvOrigin?.text = data
}
val newBody =
AesUtil.encrypt(data, KEY) ?: ""
runOnUiThread {
tvNew?.text = newBody
}
val requestBody = RequestBody.create(
MediaType.parse("application/json;charset=utf-8"),
newBody
)
builder.post(requestBody)
}
}
originalRequest = builder.build()
val response = chain.proceed(originalRequest)
val responseBody = response.body()
var charset = Charset.forName("utf-8")
val source = responseBody?.source()
source?.request(Long.MAX_VALUE)
val buffer = source?.buffer
val contentType = responseBody?.contentType()
if (contentType != null) {
try {
charset = contentType.charset(Charset.forName("utf-8"))
} catch (e: Exception) {
return@Interceptor response
}
}
val contentLength = responseBody?.contentLength()
if (contentLength != 0L) {
val oriData = buffer?.clone()?.readString(charset)
Log.e("TAG", oriData.toString())
val data = AesUtil.decrypt(oriData, KEY) ?: ""
val newResponse = okhttp3.Response.Builder()
newResponse.code(response.code())
newResponse.message(response.message())
val responseBody1 =
ResponseBody.create(MediaType.parse("application/json;charset=utf-8"), data)
newResponse.body(responseBody1)
newResponse.request(originalRequest)
newResponse.protocol(response.protocol())
return@Interceptor newResponse.build()
}
response
结语
新技能get!+1
接口加密,尤其是AES加密其实不难,将需求一步步剥离出来就好处理了,而且加解密重点是密钥。