Java使用NIO进行HTTPS协议访问的时候,离不开SSLContext和SSLEngine两个类。我们只需要在Connect操作、Connected操作、Read和Write操作中加入SSL相关的处理即可。

一、连接服务器之前先初始化SSLContext并设置证书相关的操作。

1 public void Connect(String host, int port) {
2 mSSLContext = this.InitSSLContext();
3 super.Connect(host, port);
4 }

在连接服务器前先创建SSLContext对象,并进行证书相关的设置。如果服务器不是使用外部公认的认证机构生成的密钥,可以使用基于公钥CA的方式进行设置证书。如果是公认的认证证书一般只需要加载Java KeyStore即可。

    1.1 基于公钥CA

 1 public SSLContext InitSSLContext() throws NoSuchAlgorithmException{
2 // 创建生成x509证书的对象
3 CertificateFactory caf = CertificateFactory.getInstance("X.509");
4 // 这里的CA_PATH是服务器的ca证书,可以通过浏览器保存Cer证书(Base64和DER都可以)
5 X509Certificate ca = (X509Certificate)caf.generateCertificate(new FileInputStream(CA_PATH));
6 KeyStore caKs = KeyStore.getInstance("JKS");
7 caKs.load(null, null);
8 // 将上面创建好的证书设置到仓库里面,前面的`baidu-ca`只是一个别名可以任意不要出现重复即可。
9 caKs.setCertificateEntry("baidu-ca", ca);
10 TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
11 tmf.init(caKs);
12 // 最后创建SSLContext,将可信任证书列表传入。
13 SSLContext context = SSLContext.getInstance("TLSv1.2");
14 context.init(null, tmf.getTrustManagers(), null);
15 return context;
16 }

    1.2 加载Java KeyStore

 1 public SSLContext InitSSLContext() throws NoSuchAlgorithmException{
2 // 加载java keystore 仓库
3 KeyStore caKs = KeyStore.getInstance("JKS");
4 // 把生成好的jks证书加载进来
5 caKs.load(new FileInputStream(CA_PATH), PASSWORD.toCharArray());
6 // 把加载好的证书放入信任的列表
7 TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
8 tmf.init(caKs);
9 // 最后创建SSLContext,将可信任证书列表传入。
10 SSLContext context = SSLContext.getInstance("TLSv1.2");
11 context.init(null, tmf.getTrustManagers(), null);
12 return context;
13 }

二、连接服务器成功后,需要创建SSLEngine对象,并进行相关设置与握手处理。

通过第一步生成的SSLContext创建SSLSocketFactory并将当前的SocketChannel进行绑定(注:很多别人的例子都没有这步操作,如果只存在一个HTTPS的连接理论上没有问题,但如果希望同时创建大量的HTTPS请求“可能”有问题,因为SSLEngine内部使用哪个Socket进行操作数据是不确定,如果我的理解有误欢迎指正)。

然后调用创建SSLEngine对象,并初始化操作数据的Buffer,然后开始进入握手阶段。(注:这里创建的Buffer主要用于将应用层数据加密为网络数据,将网络数据解密为应用层数据使用:“密文与明文”)。

 1 public final void OnConnected() {
2 super.OnConnected();
3 // 设置socket,并创建SSLEngine,开始握手
4 SSLSocketFactory fx = mSSLContext.getSocketFactory();
5 // 这里将自己的channel传进去
6 fx.createSocket(mSocketChannel.GetSocket(), mHost, mPort, false);
7 mSSLEngine = this.InitSSLEngine(mSSLContext);
8 // 初始化使用的BUFFER
9 int appBufSize = mSSLEngine.getSession().getApplicationBufferSize();
10 int netBufSize = mSSLEngine.getSession().getPacketBufferSize();
11 mAppDataBuf = ByteBuffer.allocate(appBufSize);
12 mNetDataBuf = ByteBuffer.allocate(netBufSize);
13 pAppDataBuf = ByteBuffer.allocate(appBufSize);
14 pNetDataBuf = ByteBuffer.allocate(netBufSize);
15 // 初始化完成,准备开启握手
16 mSSLInitiated = true;
17 mSSLEngine.beginHandshake();
18 this.ProcessHandShake(null);
19 }

三、进行握手操作

下图简单展示了握手流程,由客户端发起,通过一些列的数据交换最终完成握手操作。要成功与服务器建立连接,握手流程是非常重要的环节,幸好SSEngine内部已经实现了证书验证、交换等步骤,我们只需要在其上层执行特定的行为(握手状态处理)。

3.1 握手相关状态(来自getHandshakeStatus方法)

NEED_WRAP 当前握手状态表示需要加密数据,即将要发送的应用层数据加密输出为网络层数据,并执行发送操作。

NEED_UNWRAP 当前握手状态表示需要对数据进行解密,即将收到的网络层数据解密后成应用层数据。

NEED_TASK 当前握手状态表示需要执行任务,因为有些操作可能比较耗时,如果不希望造成阻塞流程就需要开启异步任务进行执行。

 FINISHED 当前握手已完成

NOT_HANDSHAKING 表示不需要握手,这个主要是再次连接时,为了加快速度而跳过握手流程。

    3.2 处理握手的方法

以下代码展示了握手流程中的各种状态的处理,主要的逻辑就是如果需要加密就执行加密操作,如果需要执行解密就执行解密操作(废话@_@!)。

 1 protected void ProcessHandShake(SSLEngineResult result){
2 if(this.isClosed() || this.isShutdown()) return;
3 // 区分是来此WRAP UNWRAP调用,还是其他调用
4 SSLEngineResult.HandshakeStatus status;
5 if(result != null){
6 status = result.getHandshakeStatus();
7 }else{
8 status = mSSLEngine.getHandshakeStatus();
9 }
10 switch(status)
11 {
12 // 需要加密
13 case NEED_WRAP:
14 //判断isOutboundDone,当true时,说明已经不需要再处理任何的NEED_WRAP操作了.
15 // 因为已经显式调用过closeOutbound,且就算执行wrap,
16 // SSLEngineReulst.STATUS也一定是CLOSED,没有任何意义
17 if(mSSLEngine.isOutboundDone()){
18 // 如果还有数据则发送出去
19 if(mNetDataBuf.position() > 0) {
20 mNetDataBuf.flip();
21 mSocketChannel.WriteAndFlush(mNetDataBuf);
22 }
23 break;
24 }
25 // 执行加密流程
26 this.ProcessWrapEvent();
27 break;
28 // 需要解密
29 case NEED_UNWRAP:
30 //判断inboundDone是否为true, true说明peer端发送了close_notify,
31 // peer发送了close_notify也可能被unwrap操作捕获到,结果就是返回的CLOSED
32 if(mSSLEngine.isInboundDone()){
33 //peer端发送关闭,此时需要判断是否调用closeOutbound
34 if(mSSLEngine.isOutboundDone()){
35 return;
36 }
37 mSSLEngine.closeOutbound();
38 }
39 break;
40 case NEED_TASK:
41 // 执行异步任务,我这里是同步执行的,可以弄一个异步线程池进行。
42 Runnable task = mSSLEngine.getDelegatedTask();
43 if(task != null){
44 task.run();
45 // executor.execute(task); 这样使用异步也是可以的,
46 //但是异步就需要对ProcessHandShake的调用做特殊处理,因为异步的,像下面这直接是会导致疯狂调用。
47 }
48 this.ProcessHandShake(null); // 继续处理握手
49 break;
50 case FINISHED:
51 // 握手完成
52 mHandshakeCompleted = true;
53 this.OnHandCompleted();
54 return;
55 case NOT_HANDSHAKING:
56 // 不需要握手
57 if(!mHandshakeCompleted)
58 {
59 mHandshakeCompleted = true;
60 this.OnHandCompleted();
61 }
62 return;
63 }
64 }

四、数据的发送与接收

握手成功后就可以进行正常的数据发送与接收,但是需要额外在数据发送的时候进行加密操作,数据接收后进行解密操作。

这里需要额外说明一下,在握手期间也是会需要读取数据的,因为服务器发送过来的数据需要我们执行读取并解密操作。而这个操作在一些其他的例子中直接使用了阻塞的读取方式,我这里则是放在OnRead事件调用后进行处理,这样才符合NIO模型。

    4.1 加密操作(SelectionKey.OP_WRITE)

 1 protected void ProcessWrapEvent(){
2 if(this.isClosed() || this.isShutdown()) return;
3 SSLEngineResult result = mSSLEngine.wrap(mAppDataBuf, mNetDataBuf);
4 // 处理result
5 if(ProcessSSLStatus(result, true)){
6 mNetDataBuf.flip();
7 mSocketChannel.WriteAndFlush(mNetDataBuf);
8 // 发完成后清空buffer
9 mNetDataBuf.clear();
10 }
11 mAppDataBuf.clear();
12 // 如果没有握手完成,则继续调用握手处理
13 if(!mHandshakeCompleted)
14 this.ProcessHandShake(result);
15 }

    4.2 解密操作(SelectionKey.OP_READ)

 1 protected void ProcessUnWrapEvent(){
2 if(this.isClosed() || this.isShutdown()) return;
3 do{
4 // 执行解密操作
5 SSLEngineResult res = mSSLEngine.unwrap(pNetDataBuf, pAppDataBuf);
6 if(!ProcessSSLStatus(res, false))
7 // 这里不需要对`pNetDataBuf`进行处理,因为ProcessSSLStatus里面已经做好处理了。
8 return;
9 if(res.getStatus() == Status.CLOSED)
10 break;
11 // 未完成握手时,需要继续调用握手处理
12 if(!mHandshakeCompleted)
13 this.ProcessHandShake(res);
14 }while(pNetDataBuf.hasRemaining());
15 // 数据都解密完了,这个就可以清空了。
16 if(!pNetDataBuf.hasRemaining())
17 pNetDataBuf.clear();
18 }

文章来自我的公众号,大家如果有兴趣可以关注,具体扫描关注下图。

Java通过SSLEngine与NIO实现HTTPS访问的更多相关文章

  1. Tomcat创建HTTPS访问,java访问https

    一 https和ssL HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的 ...

  2. Java网络编程和NIO详解开篇:Java网络编程基础

    Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...

  3. Java网络编程和NIO详解8:浅析mmap和Direct Buffer

    Java网络编程与NIO详解8:浅析mmap和Direct Buffer 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NI ...

  4. Java网络编程和NIO详解9:基于NIO的网络编程框架Netty

    Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...

  5. Java网络编程和NIO详解6:Linux epoll实现原理详解

    Java网络编程和NIO详解6:Linux epoll实现原理详解 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO h ...

  6. Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO

    Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO Java 非阻塞 IO 和异步 IO 转自https://www.javadoop.com/post/nio-and-aio 本系 ...

  7. Java网络编程和NIO详解4:浅析NIO包中的Buffer、Channel 和 Selector

    Java网络编程与NIO详解4:浅析NIO包中的Buffer.Channel 和 Selector 转自https://www.javadoop.com/post/nio-and-aio 本系列文章首 ...

  8. Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制

    Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制 JAVA 中原生的 socket 通信机制 摘要:本文属于原创,欢迎转载,转载请保留出处:https://github.co ...

  9. wdcp 下apache模式开启https访问,支持多站点

    1.vi conf/httpd.conf 查找 #Include conf/extra/httpd-ssl.conf (删除行首的配置语句注释符号"#"保存退出) 2.vi con ...

随机推荐

  1. Linux:linux服务器稳定性压力测试工具stress安装与使用

    stress是一个linux下的压力测试工具,专门为那些想要测试自己的系统,完全高负荷和监督这些设备运行的用户. 1. stress1.0.4下载地址 下载:https://fossies.org/l ...

  2. MyEclipse中,编写properties文件,输入中文显示乱码

    我在properties文件中输出中文,结果显示的是乱码,额......好吧,其实不是乱码,哪有这么规范的乱码 其实是在输入中文时发生了转码,就是下面这个样子: 字符集不支持中文,修改方法: 选中你工 ...

  3. 续PA协商过程

    续PA协商过程 当sw3的接口恢复之后会发生2中情况. ①sw3的G0/0/2口先发BPDU ②sw3的G0/0/3口先发BPDU sw3先发送BPDU sw3和sw1的交互过程: sw3的2口恢复后 ...

  4. AspNetCore&MassTransit Courier实现分布式事务

    在之前的一篇博文中,CAP框架可以方便我们实现非实时.异步场景下的最终一致性,而有些用例总是无法避免的需要在实时.同步场景下进行,可以借助Saga事务来解决这一困扰.在一些博文和仓库中也搜寻到了.Ne ...

  5. Vue权限路由实现总结

    前言 年前完工了做了半年的铁路后台管理系统,系统整体业务比较复杂,这也是我到公司从 0 到 1 的 一个完整系统实践,做这个系统过程中踩了不少坑,也学到了很多. 做完这个系统没多久,紧接着又一个系统来 ...

  6. 建立第一个SCRAPY的具体过程

    1.安装SCRAPY2.进入CMD:执行:SCRAPY显示: Scrapy 1.8.0 - no active project Usage: scrapy <command> [optio ...

  7. c语言:DEV-C++5.10调试设置

    DEV-C++调试设置方法:默认不能调试,打开调试的方法: 1.点击"工具"菜单--编译选项--"代码生成/优化"--连接器--"产生调试信息&quo ...

  8. 在deeping上安装mariadb

    1,安装的官网参考:有安装的命令和指导https://downloads.mariadb.org/mariadb/repositories/#distro=Debian&distro_rele ...

  9. 为什么每次下载后必须关闭掉IO流(十五)

    读一个文件,忘记关闭了流,你在操作系统里对这个文件的写,删除等操作就会报错,告诉你这个文件被某个进程占用,这是为什么呢? java是从c++设计来的,但是无论是C语言还是C++,都需要手动释放内存,j ...

  10. C#计算复利方法

    复利即是指利滚知利 如存入1000,年利息回0.003,存了答10年,则调用fl(0.003,1000,10); double fl(double rate,double cash,int times ...