目录
使用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认证以及客户端证书导入方法》
准备工作
Keytool
keytool是JDK自带的证书管理工具,一般在 /java/bin/目录,配置了JDK环境变量都是可以直接执行的。使用 “keytool -command_name -help” 获取 command_name 的用法。具体可参考:
Protecle
protecle是第三方证书转换生成工具,由于Android平台不识别.keystore和.jks格式的证书库文件,因此Android平台引入一种的证书库格式:BKS,protecle工具可将jks/p12证书转bks证书。
下载链接:
链接: 百度网盘 请输入提取码 提取码: pvkw
生成HTTPS所需证书
使用Keytool生成jks证书
在终端执行命令:keytool -genkey -alias server -keyalg RSA -keystore server.jks
在当前目录下生成别名server,加密算法为RSA,证书名称为server的jks证书。
看不懂命令?可先阅读参考:孤傲苍狼的《Java制作证书的工具keytool用法总结》
注意:
1、密钥库的密码至少必须6个字符,可以是纯数字或者字母或者数字和字母的组合等等,我这里填的是123456;
2、”名字与姓氏”应该是输入域名,而不是我们的个人姓名,由于项目的实际应用场景只有ip没有域名,因此这里填了个*号代表全匹配,其它的可以不用填,一路Yes或者回车就可以了。当然实际项目肯定是要好好填写的。
证书生成后有个以下提示,可以不用理会,实际测试没有影响。
Warning:JKS 密钥库使用专用格式。建议使用 “keytool -importkeystore -srckeystore server.jks -destkeystore server.jks -deststoretype pkcs12” 迁移到行业标准格式 PKCS12。
我们还可以执行keytool -v -list -keystore server.jks
查看证书信息,密钥库口令可不填。
使用Protecle将JSK证书转BKS证书
下载protecle.zip压缩包,解压。使用java -jar portecle.jar
运行此工具。
点击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申请的,所以浏览器根证书不信任,这里我们手动点继续连接。
返回成功。
遇到的问题
你可能会遇到java.util.concurrent.ExecutionException: javax.net.ssl.SSLHandshakeException: Handshake failed
。
请检查:
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认证以及客户端证书导入方法
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/116893.html