Java通过SSLEngine与NIO实现HTTPS访问的操作方法

网友投稿 286 2022-12-16

Java通过SSLEngine与NIO实现HTTPS访问的操作方法

目录一、连接服务器之前先初始化SSLContext并设置证书相关的操作。1.1 基于公钥CA1.2 加载java KeyStore二、连接服务器成功后,需要创建SSLEngine对象,并进行相关设置与握手处理。三、进行握手操作3.1 握手相关状态(来自getHandshakeStatus方法)3.2处理握手的方法四、数据的发送与接收4.1加密操作(SelectionKey.OP_WRITE)4.2 解密操作(SelectionKey.OP_READ)

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

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

public void Connect(String host, int port) {

mSSLContext = this.InitSSLContext();

super.Connect(host, port);

}

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

1.1 基于公钥CA

public SSLContext InitSSLContext() throws NoSuchAlgorithmException{

// 创建生成x509证书的对象

CertificateFactory caf = CertificateFactory.getInstance("X.509");

// 这里的CA_PATH是服务器的ca证书,可以通过浏览器保存Cer证书(Base64和DER都可以)

X509Certificate ca = (X509Certificate)caf.generateCertificate(new FileInputStream(CA_PATH));

KeyStore caKs = KeyStore.getInstance("JKS");

caKs.load(null, null);

// 将上面创建好的证书设置到仓库里面,前面的`baidu-ca`只是一个别名可以任意不要出现重复即可。

caKs.setCertificateEntry("baidu-ca", ca);

TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");

tmf.init(caKs);

// 最后创建SSLContext,将可信任证书列表传入。

SSLContext context = SSLContext.getInstance("TLSv1.2");

context.init(null, tmf.getTrustManagers(), null);

return context;

}

1.2 加载Java KeyStore

public SSLContext InitSSLContext() throws NoSuchAlgorithmException{

// 加载java keystore 仓库

KeyStore caKs = KeyStore.getInstance("JKS");

// 把生成好的jks证书加载进来

caKs.load(new FileInputStream(CA_PATH), PASSWORD.toCharArray());

// 把加载好的证书放入信任的列表

TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");

tmf.init(caKs);

// 最后创建SSLContext,将可信任证书列表传入。

SSLContext context = SSLContext.getInstance("TLSv1.2");

context.init(null, tmf.getTrustManagers(), null);

return context;

}

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

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

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

public final void OnConnected() {

super.OnConnected();

// 设置socket,并创建SSLEngine,开始握手

SSLSocketFactory fx = mSSLContext.getSocketFactory();

// 这里将自己的channel传进去

fx.createSocket(mSocketChannel.GetSocket(), mHost, mPort, false);

mSSLEngine = this.InitSSLEngine(mSSLContext);

// 初始化使用的BUFFER

int appBufSize = mSSLEngine.getSession().getApplicationBufferSize();

int netBufSize = mSSLEngine.getSession().getPacketBufferSize();

mAppDataBuf = Byhttp://teBuffer.allocate(appBufSize);

mNetDataBuf = ByteBuffer.allocate(netBufSize);

pAppDataBuf = ByteBuffer.allocate(appBufSize);

pNetDataBuf http://= ByteBuffer.allocate(netBufSize);

// 初始化完成,准备开启握手

mSSLInitiated = true;

mSSLEngine.beginHandshake();

this.ProcessHandShake(null);

}

三、进行握手操作

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

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

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

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

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

FINISHED当前握手已完成

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

3.2处理握手的方法

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

protected void ProcessHandShake(SSLEngineResult result){

if(this.isClosed() || this.isShutdown()) return;

// 区分是来此WRAP UNWRAP调用,还是其他调用

SSLEngineResult.HandshakeStatus status;

if(result != null){

status = result.getHandshakeStatus();

}else{

status = mSSLEngine.getHandshakeStatus();

}

switch(status)

{

// 需要加密

case NEED_WRAP:

//判断isOutboundDone,当true时,说明已经不需要再处理任何的NEED_WRAP操作了.

// 因为已经显式调用过closeOutbound,且就算执行wrap,

// SSLEngineReulst.STATUS也一定是CLOSED,没有任何意义

if(mSSLEngine.isOutboundDone()){

// 如果还有数据则发送出去

if(mNetDataBuf.position() > 0) {

mNetDataBuf.flip();

mSocketChannel.WriteAndFlush(mNetDataBuf);

}

break;

}

// 执行加密流程

this.ProcessWrapEvent();

break;

// 需要解密

case NEED_UNWRAP:

//判断inboundDone是否为true, true说明peer端发送了close_notify,

// peer发送了close_notify也可能被unwrap操作捕获到,结果就是返回的CLOSED

if(mSSLEngine.isInboundDone()){

//peer端发送关闭,此时需要判断是否调用closeOutbound

if(mSSLEngine.isOutboundDone()){

return;

}

mSSLEngine.closeOutbound();

}

break;

case NEED_TASK:

// 执行异步任务,我这里是同步执行的,可以弄一个异步线程池进行。

Runnable task = mSSLEngine.getDelegatedTask();

if(task != null){

task.run();

// executor.execute(task); 这样使用异步也是可以的,

//但是异步就需要对ProcessHandShake的调用做特殊处理,因为异步的,像下面这直接是会导致疯狂调用。

}

this.ProcessHandShake(null); // 继续处理握手

break;

case FINISHED:

// 握手完成

mHandshakeCompleted = true;

this.OnHandCompleted();

return;

case NOT_HANDSHAKING:

// 不需要握手

if(!mHandshakeCompleted)

{

mHandshakeCompleted = true;

this.OnHandCompleted();

}

return;

}

}

四、数据的发送与接收

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

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

4.1加密操作(SelectionKey.OP_WRITE)

protected void ProcessWrapEvent(){

if(this.isClosed() || this.isShutdown()) return;

SSLEngineResult result = mSSLEngine.wrap(mAppDataBuf, mNetDataBuf);

// 处理result

if(ProcessSSLStatus(result, true)){

mNetDataBuf.flip();

mSocketChannel.WriteAndFlush(mNetDataBuf);

// 发完成后清空buffer

mNetDataBuf.clear();

}

mAppDataBuf.clear();

// 如果没有握手完成,则继续调用握手处理

if(!mHandshakeCompleted)

this.ProcessHandShake(result);

}

4.2 解密操作(SelectionKey.OP_READ)

protected void ProcessUnWrapEvent(){

if(this.isClosed() || this.isShutdown()) return;

do{

// 执行解密操作

SSLEngineResult res = mSSLEngine.unwrap(pNetDataBuf, pAppDataBuf);

if(!ProcessSSLStatus(res, false))

// 这里不需要对`pNetDataBuf`进行处理,因为ProcessSSLStatus里面已经做好处理了。

return;

if(res.getStatus() == Status.CLOSED)

break;

// 未完成握手时,需要继续调用握手处理

if(!mHandshakeCompleted)

this.ProcessHandShake(res);

}while(pNetDataBuf.hasRemaining());

// 数据都解密完了,这个就可以清空了。

if(!pNetDataBuf.hasRemaining())

pNetDataBuf.clear();

}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:解决springboot 2.x 里面访问静态资源的坑
下一篇:基于JavaSwing设计和实现的酒店管理系统
相关文章

 发表评论

暂时没有评论,来抢沙发吧~