Pārlūkot izejas kodu

sample: 新增示例-加密/解密拦截器

drake 2 gadi atpakaļ
vecāks
revīzija
fa68fb1dab

+ 10 - 11
docs/interceptor.md

@@ -1,11 +1,12 @@
-Net总共支持两种拦截器
+总共支持两种拦截器
 
-1. Interceptor, 支持市面上的所有OkHttp拦截器组件库, 可以修改任何请求响应信息, 可以转发请求
-2. RequestInterceptor, 更简单易用的轻量拦截器, 一般用于添加全局请求头/参数, 无法转发请求
+1. `Interceptor`, 支持市面上的所有OkHttp拦截器组件库, 更方便修改请求/响应信息, 可以转发请求
+2. `RequestInterceptor`, 部分场景更简单易用的轻量拦截器, 更方便添加全局请求头/参数, 无法转发请求
 <br>
 
-> 你的业务可能需要加密解密. 但请不要封装Post/Get等请求动作(这是不明智的做法) <br>
-  建议自定义拦截器和转换器可以应对任何项目需求, 同时更符合架构设计
+> 实际项目中可能存在需求加密请求/解密响应, 非常不建议封装Post/Get等请求动作(低扩展性/增加学习成本), 任何项目需求都可以通过自定义拦截器和转换器实现 <br>
+> [常见拦截器示例](https://github.com/liangjingkanji/Net/tree/master/sample/src/main/java/com/drake/net/sample/interceptor)
+
 
 ## 拦截器
 
@@ -52,16 +53,14 @@ class RefreshTokenInterceptor : Interceptor {
 
 | 函数 | 描述 |
 |-|-|
-| peekString | 可以复制截取RequestBody/ResponseBody, 且返回String |
-| logString | 等效于上面函数, 但是Response仅支持文本/JSON, Request仅支持FormBody |
+| peekString | 可以截取复制`RequestBody/ResponseBody`, 并返回String |
+| logString | 等同`peekString`, 但仅支持content-type为xml/html/json/plain类型的数据 |
 
 ## 请求拦截器
 
-RequestInterceptor属于轻量级的请求拦截器, 在每次请求的时候该拦截器都会被触发, 但是无法修改响应信息 <br>
-
-一般用于添加全局的参数和请求头
+RequestInterceptor属于轻量级的请求拦截器, 在每次请求的时候该拦截器都会被触发(无法修改响应信息), 方便添加全局请求头/参数
 
-初始化时添加请求拦截器的示例代码
+示例
 
 ```kotlin
 NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) {

+ 2 - 2
sample/src/main/java/com/drake/net/sample/base/App.kt

@@ -32,7 +32,7 @@ import com.drake.net.sample.BuildConfig
 import com.drake.net.sample.R
 import com.drake.net.sample.constants.Api
 import com.drake.net.sample.converter.SerializationConverter
-import com.drake.net.sample.interfaces.MyRequestInterceptor
+import com.drake.net.sample.interceptor.GlobalHeaderInterceptor
 import com.drake.net.sample.mock.MockDispatcher
 import com.drake.statelayout.StateConfig
 import com.drake.tooltip.dialog.BubbleDialog
@@ -79,7 +79,7 @@ class App : Application() {
             }
 
             // 添加请求拦截器, 可配置全局/动态参数
-            setRequestInterceptor(MyRequestInterceptor())
+            setRequestInterceptor(GlobalHeaderInterceptor())
 
             // 数据转换器
             setConverter(SerializationConverter())

+ 66 - 0
sample/src/main/java/com/drake/net/sample/interceptor/EncryptDataInterceptor.kt

@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 Drake, https://github.com/liangjingkanji
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.drake.net.sample.interceptor
+
+import com.drake.net.request.MediaConst
+import com.drake.net.sample.utils.AESUtils
+import okhttp3.Interceptor
+import okhttp3.RequestBody.Companion.toRequestBody
+import okhttp3.Response
+import okhttp3.ResponseBody.Companion.toResponseBody
+import okio.Buffer
+
+/**
+ * 演示如何加密请求参数/解密响应数据
+ */
+class EncryptDataInterceptor : Interceptor {
+    override fun intercept(chain: Interceptor.Chain): Response {
+        var request = chain.request()
+
+        // 加密, 仅加密 POST请求/JSON参数类型
+        if (request.method == "POST" && request.header("Content-Type") == MediaConst.JSON.toString()) {
+            val body = request.body
+            if (body != null) {
+                val buff = Buffer()
+                body.writeTo(buff)
+                val json = buff.readUtf8()
+                if (json.isNotBlank()) {
+                    val encryptJson = AESUtils.encrypt(json)
+                    val newBody = encryptJson.toRequestBody(MediaConst.JSON)
+                    request = request.newBuilder().post(newBody).build()
+                }
+            }
+        }
+
+        var response = chain.proceed(request)
+
+        // 解密, 仅解密JSON响应类型
+        if (response.header("Content-Type") == MediaConst.JSON.toString()) {
+            val body = response.body
+            if (body != null) {
+                val json = body.string()
+                if (json.isNotBlank()) {
+                    val decryptJson = AESUtils.decrypt(json)
+                    val requestBody = decryptJson.toResponseBody(MediaConst.JSON)
+                    response = response.newBuilder().body(requestBody).build()
+                }
+            }
+        }
+
+        return response
+    }
+}

+ 4 - 7
sample/src/main/java/com/drake/net/sample/interfaces/MyRequestInterceptor.kt → sample/src/main/java/com/drake/net/sample/interceptor/GlobalHeaderInterceptor.kt

@@ -1,19 +1,16 @@
-package com.drake.net.sample.interfaces
+package com.drake.net.sample.interceptor
 
 import com.drake.net.interceptor.RequestInterceptor
 import com.drake.net.request.BaseRequest
 import com.drake.net.sample.constants.UserConfig
 
 
-/** 请求拦截器, 一般用于添加全局参数 */
-class MyRequestInterceptor : RequestInterceptor {
+/** 演示添加全局请求头/参数 */
+class GlobalHeaderInterceptor : RequestInterceptor {
 
     /** 本方法每次请求发起都会调用, 这里添加的参数可以是动态参数 */
     override fun interceptor(request: BaseRequest) {
-        // 仅请求动作没有添加时才会添加默认请求头
-        if (request.headers()["client"] == null) {
-            request.addHeader("client", "Android")
-        }
+        request.setHeader("client", "Android")
         request.setHeader("token", UserConfig.token)
     }
 }

+ 1 - 1
sample/src/main/java/com/drake/net/sample/interfaces/RefreshTokenInterceptor.kt → sample/src/main/java/com/drake/net/sample/interceptor/RefreshTokenInterceptor.kt

@@ -15,7 +15,7 @@
  *
  */
 
-package com.drake.net.sample.interfaces
+package com.drake.net.sample.interceptor
 
 import com.drake.net.Net
 import com.drake.net.sample.constants.UserConfig

+ 50 - 0
sample/src/main/java/com/drake/net/sample/utils/AESUtils.kt

@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 Drake, https://github.com/liangjingkanji
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.drake.net.sample.utils
+
+import okio.ByteString
+import okio.ByteString.Companion.decodeHex
+import javax.crypto.Cipher
+import javax.crypto.spec.SecretKeySpec
+
+object AESUtils {
+
+    private const val KEY = "123456789"
+    private const val IV = "123456789"
+
+    fun encrypt(data: String): String {
+        val key = KEY.decodeHex()
+        val iv = IV.decodeHex()
+        val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
+        val keySpec = SecretKeySpec(key.toByteArray(), "AES")
+        val ivSpec = javax.crypto.spec.IvParameterSpec(iv.toByteArray())
+        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec)
+        val encrypted = cipher.doFinal(data.toByteArray())
+        return ByteString.of(*encrypted).hex()
+    }
+
+    fun decrypt(data: String): String {
+        val key = KEY.decodeHex()
+        val iv = IV.decodeHex()
+        val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
+        val keySpec = SecretKeySpec(key.toByteArray(), "AES")
+        val ivSpec = javax.crypto.spec.IvParameterSpec(iv.toByteArray())
+        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
+        val encrypted = cipher.doFinal(data.decodeHex().toByteArray())
+        return String(encrypted)
+    }
+}