最近研究用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失效的异常,连接并登陆服务器的代码如下:
hostname,port,username,password分别是提供的ip地址,端口,用户和口令密码,/** * 登陆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(); } }
返回的是SshClient对象,并可判断是基于SSH1还是SSH2协议。针对密码校验方式就需要实例化一个PasswordAuthentication对象,并setPassword口令。通过ssh.authenticate(pwd)进行校验,如果结果为SshAuthentication.COMPLETE表示口令和密码通过,则表明连接上了该远程服务器。ssh = con.connect(transport, username, true);
下段是发送执行命令的代码:
首先ssh.isAuthenticated()判断是否认证过,再利用ChannelAdapter作为SshSession的监听器,final SshSession session = ssh/** * 发送命令并执行,可执行以";"分隔的多个命令 * * @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(); }
.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高手指正。