| |
|
|
开发:
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年12日历 | -2025/12/14 11:11:20- |
|
| 网站联系: qq:121756557 email:121756557@qq.com IT数码 |