我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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内部已经实现了证书验证、交换等步骤,我们只需要在其上层执行特定的行为(握手状态处理)。

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();
}

到此这篇关于Java通过SSLEngine与NIO实现HTTPS访问的文章就介绍到这了,更多相关Java通过SSLEngine与NIO实现HTTPS访问内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

golang通过http访问外部网址的操作方法

本文详细介绍了Go语言中使用HTTP客户端与外部网址交互的方法。通过net/http包,开发者可以创建HTTP请求,发送请求并处理响应(包括状态代码、响应头和响应体)。文末还提供了示例代码和最佳实践建议。
golang通过http访问外部网址的操作方法
2024-04-02

ASP.NETCore6开启文件服务允许通过url访问附件的操作方法

开启ASP.NETCore6文件服务本指南详细介绍了如何开启ASP.NETCore6中的文件服务,以通过URL访问附件。通过步骤和代码示例,用户可以配置文件服务,设置文件位置,自定义MIME类型映射,启用范围验证等,从而安全高效地提供文件。
ASP.NETCore6开启文件服务允许通过url访问附件的操作方法
2024-04-02

Java实现文件上传到服务器本地并通过url访问的方法步骤

最近项目中使用到了文件上传到服务器的功能,下面这篇文章主要给大家介绍了关于Java实现文件上传到服务器本地并通过url访问的方法步骤,文中通过图文以及实例代码介绍的非常详细,需要的朋友可以参考下
2023-05-16

使用路由器功能实现主机跨网访问的操作方法

  路由器是互联网络中必不可少的网络设备之一,路由器是一种连接多个网络或网段的网络设备,它能将不同网络或网段之间的数据信息进行"翻译",以使它们能够相互"读"懂对方的数据,从而构成一个更大的网络,使整个网络互通数据,提高数据传输效率。在这里,小编为大家分享的教程是:使用路由器功能实现主机跨网访问的操作方法。  我们应该
使用路由器功能实现主机跨网访问的操作方法
2024-04-18

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录