Java通过SSLEngine与NIO实现HTTPS访问

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: Java使用NIO进行HTTPS协议访问的时候,离不开SSLContext和SSLEngine两个类。我们只需要在Connect操作、Connected操作、Read和Write操作中加入SSL相关的处理即可。

Java通过SSLEngine与NIO实现HTTPS访问


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,然后开始进入握手阶段。(注:这里创建的Buffer主要用于将应用层数据加密为网络数据,将网络数据解密为应用层数据使用:“密文与明文”)。


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 = ByteBuffer.allocate(appBufSize);
  mNetDataBuf = ByteBuffer.allocate(netBufSize);
  pAppDataBuf = ByteBuffer.allocate(appBufSize);
  pNetDataBuf = ByteBuffer.allocate(netBufSize);
  // 初始化完成,准备开启握手
  mSSLInitiated = true;
  mSSLEngine.beginHandshake();
  this.ProcessHandShake(null);
}


三、进行握手操作


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


微信图片_20220424142921.png

图片源自网络


   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)


五、最后


   如果本篇文章对你有所帮助,欢迎微信搜索"游戏测试开发"关注一起沟通交流。

相关文章
|
12天前
|
Web App开发 算法 应用服务中间件
nginx开启局域网https访问
【10月更文挑战第22天】为了调试WebRTC功能,需要在局域网内搭建HTTPS协议。具体步骤包括:在已部署Nginx和安装OpenSSL的环境中生成私钥、证书签名请求和自签名证书;将生成的文件放置到Nginx的证书目录并修改Nginx配置文件,最后重启Nginx服务。注意,自签名证书不受第三方机构认可,如需正式使用,需向CA申请签名。
|
15天前
|
安全 网络安全 数据安全/隐私保护
政务内网实现https访问教程
政务内网实现HTTPS访问需经过多个步骤:了解HTTPS原理,选择并申请适合的SSL证书,配置SSL证书至服务器,设置端口映射与访问控制,测试验证HTTPS访问功能,注意证书安全性和兼容性,定期备份与恢复。这些措施确保了数据传输的安全性,提升了政务服务的效率与安全性。
|
11天前
|
安全 网络安全 数据安全/隐私保护
内网IP地址实现HTTPS加密访问教程
在内网环境中,为确保数据传输的安全性,绑定SSL证书搭建HTTPS服务器至关重要。本文介绍了内网IP地址的前期准备、申请SSL证书的步骤以及客户端配置方法。具体包括选择合适的CA、注册账号、提交申请、下载证书,并在客户端导入根证书,确保通信数据的安全加密。推荐使用JoySSL提供的技术解决方案,确保内网设备通信安全。
内网IP地址实现HTTPS加密访问教程
|
12天前
|
消息中间件 缓存 Java
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
零拷贝技术 Zero-Copy 是指计算机执行操作时,可以直接从源(如文件或网络套接字)将数据传输到目标缓冲区, 而不需要 CPU 先将数据从某处内存复制到另一个特定区域,从而减少上下文切换以及 CPU 的拷贝时间。
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
|
17天前
|
SQL Java 数据库连接
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率。本文介绍了连接池的工作原理、优势及实现方法,并提供了HikariCP的示例代码。
31 3
|
19天前
|
SQL Java 数据库连接
打破瓶颈:利用Java连接池技术提升数据库访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,避免了频繁的连接建立和断开,显著提升了数据库访问效率。常见的连接池库包括HikariCP、C3P0和DBCP,它们提供了丰富的配置选项和强大的功能,帮助优化应用性能。
38 2
|
29天前
|
Java
Java访问外网图片地址时,如何添加代理?
【10月更文挑战第14天】Java访问外网图片地址时,如何添加代理?
22 2
|
1月前
|
Java
让星星⭐月亮告诉你,Java NIO之Buffer详解 属性capacity/position/limit/mark 方法put(X)/get()/flip()/compact()/clear()
这段代码演示了Java NIO中`ByteBuffer`的基本操作,包括分配、写入、翻转、读取、压缩和清空缓冲区。通过示例展示了`position`、`limit`和`mark`属性的变化过程,帮助理解缓冲区的工作原理。
28 2
|
2月前
|
存储 网络协议 Java
Java NIO 开发
本文介绍了Java NIO(New IO)及其主要组件,包括Channel、Buffer和Selector,并对比了NIO与传统IO的优势。文章详细讲解了FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel及Pipe.SinkChannel和Pipe.SourceChannel等Channel实现类,并提供了示例代码。通过这些示例,读者可以了解如何使用不同类型的通道进行数据读写操作。
Java NIO 开发
|
1月前
|
小程序 Java
小程序访问java后台失败解决方案
小程序访问java后台失败解决方案
44 2