maverick组件实现JAVA SSH协议初探

来源:互联网 发布:mac快捷键高清壁纸 编辑:程序博客网 时间:2024/04/27 00:38

        最近研究用SSH协议访问远程Linux机器,采用最普通的用户帐号和口令连接方式,端口为默认的22,以及需要执行的shell命令。我google了下用java 实现的SSH实现方式,找到了一款商业软件J2SSH Maverick(看网站介绍,该软件就是原来sourceforge上的J2SSH的升级版,更强大,更稳定,效率更高),网站只允许试用45天,过后需要购买Licenses,网址是http://www.javassh.com/products/j2ssh-maverick,用户可以在网页右面下载最新版或者稳定版,我下载试用的是1.4.35稳定版,下面可以输入个人邮箱,系统将发送试用代码的邮件到个人邮箱。闲话少述,下面进入代码正题。

       下载下来maverick.zip差不多有5M多,里面包含dist文件夹包含JAVA和J2ME版,JAVA里面包含6个JAR文件,docs里面是api文档,examples里面是相关的使用例子,相当具有参考价值,lib里面是依赖的JAR包,有4个,分别是bcprov.jar,commons-logging-java1.1.jar,jakarta-oro.jar,log4j-java1.1.jar,我使用是将这10个jar全部引入项目的classpath里面。下面就展示下操作的代码。

        在试用组件的类之前务必加上发到邮箱中的license代码才可,否则会报license失效的异常,连接并登陆服务器的代码如下:

       

/**  * 登陆SSH服务器  */ public void login() {  com.maverick.ssh.LicenseManager.addLicense("----BEGIN 3SP LICENSE----\r\n"                 + "Product : J2SSH Maverick\r\n"                 + "Licensee: 455607334@qq.com\r\n"                 + "Comments: 211.137.58.245\r\n"                 + "Type    : Evaluation License\r\n"                 + "Created : 07-Sep-2012\r\n"                 + "Expires : 22-Oct-2012\r\n"                 + "\r\n"                 + "37872030DA4FA3DBD9FF0D0AE6F5DF373E5A5888C7CE7E56\r\n"                 + "2DD8E38195E850FB96BA7F23FF71DD3C002168D831DD6ED4\r\n"                 + "29511478489E342D850338FC18622CBF1E28B1E1B4FF5FDA\r\n"                 + "49206DC4D649EAFA8196E351A464ABF3ED10E3C3DBAFCAF3\r\n"                 + "28D6A7F6B15DC340823F2994F6CED31CA47A8A61160AF684\r\n"                 + "E8EE5489EDE6F3C550AF9BBEB186B456799BD54E107A1D70\r\n"                 + "----END 3SP LICENSE----\r\n");  /**  * Create an SshConnector instance  */ try { SshConnector con = SshConnector.getInstance();  SocketTransport transport = new SocketTransport(hostname, port);  /*  * We must use buffered mode so that we have a background thread to  * fire data events back to us.  */ ssh = con.connect(transport, username, true);  /**  * Determine the version  */ if (ssh instanceof Ssh1Client) logger.infoT(hostname + " is an SSH1 server"); else logger.infoT(hostname + " is an SSH2 server");  PasswordAuthentication pwd = new PasswordAuthentication();  do { logger.infoT("Password: "); pwd.setPassword(password); } while (ssh.authenticate(pwd) != SshAuthentication.COMPLETE && ssh.isConnected());  } catch (Throwable e) { e.printStackTrace(); }  }

hostname,port,username,password分别是提供的ip地址,端口,用户和口令密码,
ssh = con.connect(transport, username, true);
返回的是SshClient对象,并可判断是基于SSH1还是SSH2协议。针对密码校验方式就需要实例化一个PasswordAuthentication对象,并setPassword口令。通过ssh.authenticate(pwd)进行校验,如果结果为SshAuthentication.COMPLETE表示口令和密码通过,则表明连接上了该远程服务器。

下段是发送执行命令的代码:

/**  * 发送命令并执行,可执行以";"分隔的多个命令  *   * @param command  *            发送执行的命令  * @return 返回命令的执行结果  */ public String execCommand(final String command) { final StringBuilder sb = new StringBuilder(); try { // 如果通过验证 if (ssh.isAuthenticated()) {  ChannelAdapter eventListener = new ChannelAdapter() { public void dataReceived(SshChannel channel, byte[] buf, int offset, int len) {  sb.append(new String(buf, offset, len)); }  public synchronized void channelClosed(SshChannel channel) { logger.infoT("notify!"); notifyAll(); } };  logger.infoT("start exec command!"); final SshSession session = ssh .openSessionChannel(eventListener);  PseudoTerminalModes pty = new PseudoTerminalModes(ssh); pty.setTerminalMode(PseudoTerminalModes.ECHO, false);  if (session.requestPseudoTerminal("vt100", 80, 24, 0, 0, pty)) {  session.setAutoConsumeInput(true); // logger.infoT("requestPseudoTerminal success!");  if (session.startShell()) {  logger.infoT("startShell success!");   Thread.sleep(4000); Thread t = new Thread() { public void run() {  try { if (!session.isClosed()) {   sb.delete(0, sb.length());  if (!"".equals(command)) { if (command.contains(";")) { String[] splitCommands = command .split(";"); for (String str : splitCommands) session.getOutputStream() .write((str + "\n") .getBytes()); } else session.getOutputStream() .write((command + "\n") .getBytes()); }  } } catch (IOException ex) { logger.exception(ex); } } };  t.start();  // sleep 5 秒,等待跑完 Thread.sleep(5000); } else logger.infoT("Authentication failed"); } logger.infoT("close session!"); session.close(); }  } catch (Throwable e) { e.printStackTrace(); logger.exception(e); } return sb.toString(); }
         首先ssh.isAuthenticated()判断是否认证过,再利用ChannelAdapter作为SshSession的监听器,final SshSession session = ssh
 .openSessionChannel(eventListener);并ChannelAdapter实现dataReceived方法,其中buf,offset,len为命令发送后相当于从InputStreamRead中的read方法读取的数据。再加上PseudoTerminalModes模式,避免部分ssh协议的ECHO的影响,并通过session.requestPseudoTerminal开启一个名称为vt100,高为80,宽为24的虚拟终端,然后session.startShell()开启Shell模式。下面开启一个线程通过session.getOutputStream().write(command.getBytes()),输出相关命令,下面不能立刻session.close,必须主线程睡眠一段时间,我设置的是5秒,作用是避免流里的数据没读完就关闭了导致命令结果不完整。这里没研究出更好的办法能监听Channel中的数据读完。最后必须关闭session,最后使用完后需要ssh.disconnect();

         总结,该软件是个商业收费软件,只能试用45天,如果超过45天需要购买Licenses,最便宜的好像都要999$,另外个人这里只是实现了单命令执行的一种办法,对于多命令还有待研究,关于其它PublicKey,Proxy的ssh协议校验方式还有待进一步研究,欢迎各位JAVA高手指正。