| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 网络协议 -> Android开发实践之《手摸手教你搭建移动端Https服务器》 -> 正文阅读 |
|
[网络协议]Android开发实践之《手摸手教你搭建移动端Https服务器》 |
目录 使用AndroidASync配置BKS证书实现HTTPS服务器 基础知识百度关键词:Android Http服务器,就可以搜出来很多关于搭建Android端Http的服务器的教程了,但是关于Https服务资料是比较少的。 实现HttpServer的第三方框架主要有:NanoHttpd和AndroidAsync这两个,关于她们介绍,请看billlllllllll的《在Android上实现HttpServer》。 本文主要使用AndroidAsync搭建Https服务器,关于的AndroidAsync的使用见: https://github.com/koush/AndroidAsync 关于Https相关知识可以参考sheldon_blogs的《Android : 关于HTTPS、TLS/SSL认证以及客户端证书导入方法》 准备工作Keytoolkeytool是JDK自带的证书管理工具,一般在 /java/bin/目录,配置了JDK环境变量都是可以直接执行的。使用 "keytool -command_name -help" 获取 command_name 的用法。具体可参考: Protecleprotecle是第三方证书转换生成工具,由于Android平台不识别.keystore和.jks格式的证书库文件,因此Android平台引入一种的证书库格式:BKS,protecle工具可将jks/p12证书转bks证书。 下载链接: 链接: 百度网盘 请输入提取码 提取码: pvkw 生成HTTPS所需证书使用Keytool生成jks证书在终端执行命令: 看不懂命令?可先阅读参考:孤傲苍狼的《Java制作证书的工具keytool用法总结》 注意: 1、密钥库的密码至少必须6个字符,可以是纯数字或者字母或者数字和字母的组合等等,我这里填的是123456; 2、"名字与姓氏"应该是输入域名,而不是我们的个人姓名,由于项目的实际应用场景只有ip没有域名,因此这里填了个*号代表全匹配,其它的可以不用填,一路Yes或者回车就可以了。当然实际项目肯定是要好好填写的。 证书生成后有个以下提示,可以不用理会,实际测试没有影响。
我们还可以执行 使用Protecle将JSK证书转BKS证书下载protecle.zip压缩包,解压。使用 点击File-->Open Keystore File(ctrl+o),选中server.jks并打开。 在弹出的密码框输入密码(这里是:123456),点OK。 点击Tools-->Change Keystore Type --> BKS,这里截不了图,用你们的头脑想象就行。 在弹出的密码框输入密码(这里还是:123456),点OK。 然后就提示Change Keystore Type Successful了。 点确定,再按快捷键Ctrl+S保存,在弹出的保存对话框里填上server.bks并保存到当前目录,注意这里要带上.jks后缀名,方便后面导入到Android项目的Raw文件夹。 最后关掉protecle工具,已经在当前目录生成了bks证书。 其它博客有介绍使用p12证书转bks证书的,这里也贴下jks转p12的命令,供大家参考。 keytool -importkeystore -srckeystore server.jks -srcstoretype JKS -deststoretype PKCS12 -destkeystore server.p12 使用AndroidASync配置BKS证书实现HTTPS服务器AndroidASync的使用具体使用见:https://github.com/koush/AndroidAsync,这里展示下示例源码好了。 AndroidAsync依赖添加上以后,新建一个SSLHttpServer类,代码如下: package com.nxg.httpsserver; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.text.TextUtils; import android.util.Log; import com.koushikdutta.async.http.AsyncHttpGet; import com.koushikdutta.async.http.Multimap; import com.koushikdutta.async.http.body.AsyncHttpRequestBody; import com.koushikdutta.async.http.server.AsyncHttpServer; import com.koushikdutta.async.http.server.AsyncHttpServerRequest; import com.koushikdutta.async.http.server.AsyncHttpServerResponse; import com.koushikdutta.async.http.server.HttpServerRequestCallback; import com.nxg.httpsserver.api.ApiCodeMsg; import com.nxg.httpsserver.api.ApiResult; import java.security.KeyStore; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; /** * 轻量的Http服务器 */ public class SSLHttpServer implements HttpServerRequestCallback { private static final String TAG = "SSLHttpServer"; private static final String X509 = "X509"; private static final String PASSWORD = "123456"; public static int DEFAULT_PORT = 8888; public static final String GET_HOST_SOFTWARE_VERSION = "/getHostSoftwareVersion"; public static String URL_GET_HOST_SOFTWARE_VERSION = "https://%s:" + DEFAULT_PORT + GET_HOST_SOFTWARE_VERSION; @SuppressLint("StaticFieldLeak") private static Context mContext; public static void setContext(Context context) { SSLHttpServer.mContext = context; } @SuppressLint("StaticFieldLeak") private static SSLHttpServer mInstance; private AsyncHttpServer asyncHttpServer; public static SSLHttpServer getInstance() { if (mInstance == null) { synchronized (SSLHttpServer.class) { if (mInstance == null) { mInstance = new SSLHttpServer(); } } } return mInstance; } private SSLContext sslContext = null; private SSLHttpServer() { try { KeyManagerFactory kmf = KeyManagerFactory.getInstance(X509); KeyStore ks = KeyStore.getInstance("BKS"); ks.load(mContext.getResources().openRawResource(R.raw.server), PASSWORD.toCharArray()); kmf.init(ks, PASSWORD.toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType()); ts.load(mContext.getResources().openRawResource(R.raw.server), PASSWORD.toCharArray()); tmf.init(ts); sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); } catch (Exception e) { e.printStackTrace(); } if (asyncHttpServer == null) { asyncHttpServer = new AsyncHttpServer(); } } /** * 开启本地服务 */ public void startServer() { Log.i(TAG, "startServer: "); asyncHttpServer.addAction("OPTIONS", "[\\d\\D]*", this); asyncHttpServer.get("[\\d\\D]*", this); asyncHttpServer.post("[\\d\\D]*", this); asyncHttpServer.listenSecure(DEFAULT_PORT, sslContext); } @Override public void onRequest(AsyncHttpServerRequest request, AsyncHttpServerResponse response) { Log.d(TAG, "onRequest: uri = " + request.getPath()); String uri = request.getPath(); //这个是获取header参数的地方,一定要谨记哦 Multimap headers = request.getHeaders().getMultiMap(); if (headers != null) { Log.d(TAG, "onRequest: headers = " + headers.toString()); } //注意:这个地方是获取post请求的参数的地方,一定要谨记哦 Multimap multimap = ((AsyncHttpRequestBody<Multimap>) request.getBody()).get(); if (multimap != null) { Log.d(TAG, "onRequest: multimap = " + multimap.toString()); } //GET/POST等请求方式 String method = request.getMethod(); Log.i(TAG, "onRequest: method = " + method); //query 是GET请求方式的参数 String query = request.getQuery().toString(); Log.i(TAG, "onRequest: query = " + query); //目前主要使用GET请求 if (TextUtils.equals(method, AsyncHttpGet.METHOD)) { Log.i(TAG, "onRequest: request:" + uri); response.send(newApiResult(uri, request.getQuery())); } else { response.send(newApiResult(uri, multimap)); } } /** * 返回ApiResult的Json格式字符串 * * @param uri 接口 * @param multimap 参数 * @return String */ private static String newApiResult(String uri, Multimap multimap) { switch (uri) { case "/test": return GsonUtils.getInstance().toJson(ApiResult.success("Test is ok!")); case "/getHostSoftwareVersion": //获取软件版本信息 if (mContext == null) { return GsonUtils.getInstance().toJson(ApiResult.fail(ApiCodeMsg.REQUEST_ERROR_CONTEXT_IS_NULL)); } PackageInfo packageInfo = null; try { packageInfo = mContext.getApplicationContext().getPackageManager().getPackageInfo(mContext.getPackageName(), 0); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } String localVersion = null; if (packageInfo != null) localVersion = packageInfo.versionName; return GsonUtils.getInstance().toJson(ApiResult.success(localVersion)); default: return GsonUtils.getInstance().toJson(ApiResult.fail(ApiCodeMsg.REQUEST_ERROR_404)); } } } 导入并配置BKS证书将生成的server.bks证书导入到项目的raw文件夹,注意后缀名一定要加上.bks,否则代码里无法直接引用。 关键是这个代码: private SSLContext sslContext = null; private SSLHttpServer() { try { KeyManagerFactory kmf = KeyManagerFactory.getInstance(X509); KeyStore ks = KeyStore.getInstance("BKS"); ks.load(mContext.getResources().openRawResource(R.raw.server), PASSWORD.toCharArray()); kmf.init(ks, PASSWORD.toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType()); ts.load(mContext.getResources().openRawResource(R.raw.server), PASSWORD.toCharArray()); tmf.init(ts); sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); } catch (Exception e) { e.printStackTrace(); } if (asyncHttpServer == null) { asyncHttpServer = new AsyncHttpServer(); } } /** * 开启本地服务 */ public void startServer() { Log.i(TAG, "startServer: "); asyncHttpServer.addAction("OPTIONS", "[\\d\\D]*", this); asyncHttpServer.get("[\\d\\D]*", this); asyncHttpServer.post("[\\d\\D]*", this); asyncHttpServer.listenSecure(DEFAULT_PORT, sslContext); } //AsyncServer源码 public void listenSecure(final int port, final SSLContext sslContext) { AsyncServer.getDefault().listen(null, port, new ListenCallback() { @Override public void onAccepted(AsyncSocket socket) { AsyncSSLSocketWrapper.handshake(socket, null, port, sslContext.createSSLEngine(), null, null, false, new AsyncSSLSocketWrapper.HandshakeCallback() { @Override public void onHandshakeCompleted(Exception e, AsyncSSLSocket socket) { if (socket != null) mListenCallback.onAccepted(socket); } }); } @Override public void onListening(AsyncServerSocket socket) { mListenCallback.onListening(socket); } @Override public void onCompleted(Exception ex) { mListenCallback.onCompleted(ex); } }); } //AsyncSSLSocketWrapper源码 public static void handshake(AsyncSocket socket, String host, int port, SSLEngine sslEngine, TrustManager[] trustManagers, HostnameVerifier verifier, boolean clientMode, final HandshakeCallback callback) { AsyncSSLSocketWrapper wrapper = new AsyncSSLSocketWrapper(socket, host, port, sslEngine, trustManagers, verifier, clientMode); wrapper.handshakeCallback = callback; socket.setClosedCallback(new CompletedCallback() { @Override public void onCompleted(Exception ex) { if (ex != null) callback.onHandshakeCompleted(ex, null); else callback.onHandshakeCompleted(new SSLException("socket closed during handshake"), null); } }); try { wrapper.engine.beginHandshake(); wrapper.handleHandshakeStatus(wrapper.engine.getHandshakeStatus()); } catch (SSLException e) { wrapper.report(e); } } KeyManagerFactory类充当基于密钥内容源的密钥管理器的工厂。每个密钥管理器管理特定类型的、由安全套接字所使用的密钥内容。密钥内容是基于 KeyStore 和/或提供者特定的源。详细介绍见KeyManagerFactory。 X.509标准是密码学里公钥证书的格式标准,详细介绍见X509证书详解(中文翻译)。 KeyStore类表示密钥和证书的存储设施,详细介绍见KeyStore。 TrustManagerFactory类充当基于信任材料源的信任管理器的工厂。每个信任管理器管理特定类型的由安全套接字使用的信任材料。信任材料是基于 KeyStore 和/或提供者特定的源。详细介绍见TrustManagerFactory。 SSLContext类的实例表示安全套接字协议的实现,它充当用于安全套接字工厂或 SSLEngine 的工厂。用可选的一组密钥和信任管理器及安全随机字节源初始化此类。详细介绍见SSLContext。 SSLEngine类允许使用安全套接字层 (SSL) 或 IETF RFC 2246 "Transport Layer Security" (TLS) 协议进行安全通信,但它与传输无关。详细介绍见SSLEngine。 本文不涉及HTTPS的原理解析,感兴趣的参考Ajay的《一次HTTPS请求的过程》。 运行测试启动HttpsSever并请求接口。 package com.nxg.httpsserver; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import androidx.appcompat.app.AppCompatActivity; import com.blankj.utilcode.util.NetworkUtils; import com.koushikdutta.async.http.AsyncHttpClient; import com.koushikdutta.async.http.AsyncHttpGet; import com.koushikdutta.async.http.AsyncHttpResponse; import java.util.concurrent.ExecutionException; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private HandlerThread httpHandlerThread; private Handler httpHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (httpHandlerThread == null) { httpHandlerThread = new HandlerThread("HttpThread"); httpHandlerThread.start(); httpHandler = new Handler(httpHandlerThread.getLooper()); } init(); testRequestSSLHttpServer(); } @Override protected void onDestroy() { super.onDestroy(); if (httpHandlerThread != null) { httpHandlerThread.quit(); httpHandler.removeCallbacksAndMessages(null); httpHandlerThread = null; httpHandler = null; } } /** * 初始化并且启动HttpServer */ private void init() { Log.i(TAG, "init: "); SSLHttpClient.setContext(getApplicationContext()); SSLHttpServer.setContext(getApplicationContext()); SSLHttpServer.getInstance().startServer(); } /** * 测试请求Https接口 */ private void testRequestSSLHttpServer() { Log.i(TAG, "testRequestSSLHttpServer: "); String hostIP = NetworkUtils.getIPAddress(true); String uri = String.format(SSLHttpServer.URL_GET_HOST_SOFTWARE_VERSION, hostIP); Log.i(TAG, "testRequestSSLHttpServer: uri " + uri); if (httpHandler != null) { httpHandler.postDelayed(new Runnable() { @Override public void run() { //耗时操作 try { SSLHttpClient.getInstance().getAsyncHttpClient().executeString(new AsyncHttpGet(uri), new AsyncHttpClient.StringCallback() { @Override public void onCompleted(Exception e, AsyncHttpResponse source, String result) { Log.i(TAG, "onCompleted: " + result); } }).get(); } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); } } }, 0); } } 请求成功。 使用浏览器测试,由于server.bks证书不是通过CA申请的,所以浏览器根证书不信任,这里我们手动点继续连接。 返回成功。 遇到的问题你可能会遇到 请检查: 1、密钥库的密码至少必须6个字符,可以是纯数字或者字母或者数字和字母的组合等等,你要检查下代码里填的密码跟证书设置的密码是否匹配; 2、"名字与姓氏"应该是输入域名,而不是我们的个人姓名或其它无意义的字符,由于项目的实际应用场景只有设备ip没有域名,因此这里填了个*号代表全匹配所有的ip和域名,大家可以尝试这个方法看下这个问题是否能解决。 源码https://github.com/xiangang/AndroidDevelopmentPractices/tree/master/AndroidHttpsServer 参考资料https://github.com/NanoHttpd/nanohttpd https://github.com/koush/AndroidAsync 使用NanoHttpd在Android设备创建https/wss服务端 keytool 可视化工具 Portecle 使用教程 图文教程 微信认证开发教程 使用NanoHttpd在Android设备创建https/wss服务端 使用SSLSocket实现双向认证(keytool证书创建双向认证证书(这里有根证书)的详细步骤以及踩雷) Android 上 Https 双向通信— 深入理解KeyManager 和 TrustManagers Java制作证书的工具keytool用法总结Android : 关于HTTPS、TLS/SSL认证以及客户端证书导入方法 |
|
网络协议 最新文章 |
使用Easyswoole 搭建简单的Websoket服务 |
常见的数据通信方式有哪些? |
Openssl 1024bit RSA算法---公私钥获取和处 |
HTTPS协议的密钥交换流程 |
《小白WEB安全入门》03. 漏洞篇 |
HttpRunner4.x 安装与使用 |
2021-07-04 |
手写RPC学习笔记 |
K8S高可用版本部署 |
mySQL计算IP地址范围 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/9 2:10:50- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |