背景

在开篇之前,让我们先对 SSH 协议有个宏观的大致了解,这样更有利于我们对本文的加深了解。首先要提到的就是计算机网络协议,所谓计算机网络协议,简单的说就是定义了一套标准和规则,使得不同计算机之间能够进行正常的网络通信,不至于出现在一台机器上发出的指令到另一台机器上成了不可认的乱码,SSH 就是众多协议的其中之一。经典的七层 OSI 模型(Open System Interconnection Reference Model)出现后,大大地解决了网络互联的兼容性问题,它将网络划分成服务、接口和协议三个部分,而协议就是说明本层的服务是如何实现的。SSH、Telnet 协议则主要被使用在用户层中(如图 1 深色部分所示),即应用层、表现层和会话层。

图 1. 七层 OSI 模型

回页首

介绍 SSH

什么是 SSH

SSH(Secure Shell Protocol)是在一个不安全的网络,进行安全远程登录和其他安全网络服务的协议。这个定义出自于 IETF(Internet Engineering Task Force)。在 TCP/IP 五层模型中,SSH 是被应用于应用层和传输层的安全协议。

SSH 的优点

传统的网络传输,如:Telnet、FTP 等,采用的是明文传输数据和口令,这样很容易被黑客这样的中间人嗅探到传输过程中的数据,大大降低了网络的通信安全。而 SSH 协议则采用数据加密的方式建立起一个安全的网络传输信道,增强了数据在网络传输过程中的安全性。数据加密程度的复杂,会导致占用更多的网络资源。SSH 会对加密数据进行一定的压缩操作,从而减缓对网络带宽的占用。总结起来,SSH 的优点如下:

  • 数据加密,提高安全性
  • 数据压缩,提高网络的传输速度。

SSH 的架构

在对 SSH 有了一个初步的认识之后,我们来看看 SSH 协议是如何做到数据的安全通信。首先来看下 SSH 协议的主要架构:

图 2. SSH 协议的构成

传输层协议: 通常运行在 TCP/IP 的上层,是许多安全网络服务的基础,提供了数据加密、压缩、服务器认证以及保证数据的完整性。比如,公共密钥算法、对称加密算法、消息验证算法等。

用户认证协议:运行在 SSH 协议的传输层之上,用来检测客户端的验证方式是否合法。

连接协议:运行在用户认证层之上,提供了交互登录会话、远程命令的执行、转发 TCP/IP 连接等功能,给数据通讯提供一个安全的,可靠的加密传输信道。

SSH 的应用

在实际的工作中,很多目标机器往往是我们无法直接操作的,这些机器可能是一个公司机房的服务器,也可能是一个远在大洋彼岸的客户环境。这时候我们必须要远程登录到目标机器,执行我们需要的操作,这样不仅降低了运营成本,也提高了执行效率。我们常见的远程登录协议有 SSH、Telnet 等。如上文所提到,Telnet 使用的是明文传输,这样对别有用心的“中间人”来说就有了可乘之机,相对 Telnet 协议,SSH 协议的安全性就高了很多。这样的特性,也使得 SSH 协议迅速被推广,很多的大型项目中都或多或少的使用到了这个协议。下面本文主要讨论 SSH 协议中用户认证协议层,并且下文中统一将远程机器称为服务器(Server),本地机器称为客户端 (Client)。

SSH 的认证协议

常见的 SSH 协议认证方式有如下几种:

  • 基于口令的验证方式(password authentication method),通过输入用户名和密码的方式进行远程机器的登录验证。
  • 基于公共密钥的安全验证方式(public key authentication method),通过生成一组密钥(public key/private key)来实现用户的登录验证。
  • 基于键盘交互的验证方式(keyboard interactive authentication method),通过服务器向客户端发送提示信息,然后由客户端根据相应的信息通过手工输入的方式发还给服务器端。

SSH 认证协议的工作原理

SSH 的主要工作流程:

图 3. SSH 登录工作流程

通过这个张流程图,我们可以看出,在用户对远程机器访问的时候,首先,是得到了服务器端的一个连接句柄,这里可以理解为是一个 session,然后客户端可以通过这个句柄取得一些服务器的基本信息,如 SSH 的版本,服务器的版本信息以及一些加密的算法信息等。其次,客户端可以对这些信息作分析,来匹配当前的客户端的加密算法、验证方式是否符合服务器的配置,然后取得彼此可接受的方式,这里可以认为是双方的协商。最后,当双方达成一致后,一个安全的信道也就真正建立起来了,此时用户就可以对远程机器做想要的操作了。当我们对此有了一定的了解后,就可以初步判断,在平时工作中,我们通过 SSH 协议去连接一个远程机器报错的时候,问题出现在哪个流程上。下面通过具体的 Java 例子来讲解用户验证方式的原理。

回页首

常见认证方式的 Java 实现

在开始前,我们要做一些环境的准备工作。

  • 一台本地机器,操作系统是 Windows 用来作为客户端
  • 一台远程机器,操作系统是 Linux 用来作为服务器端
  • OpenSSH 工具
  • Putty 工具

首先,要确保服务器端上已经安装了 OpenSSH 工具,并且 SSH 的服务已经启动,可以通过如下命令来进行查看:

查看是否已经安装了 OpenSSH

清单 1. OpenSSH 版本
 # rpm -qa | grep ssh openssh-5.1p1-41.31.36 openssh-askpass-5.1p1-41.31.36

查看 SSH 服务是否启动。

清单 2. SSH 的服务状态
 #/etc/init.d/sshd status

Checking for service sshd running 在 Windows 机器,即客户端上尝试使用 Putty 工具连接远程机器。

图 4. SSH 连接成功

到目前为止,我们已经可以正常的连接到这台远程机器。下面我们就要通过 Java 代码的方式来实现我们自己的这个远程登录的操作。

验证 service name

在 SSH 协议中定义了一些消息代码,而 50 至 79 这些代码是保留给用户认证协议层使用的,而 80 以上的数字是用于协议运行的,所以如果在用户认证协议验证之前,如果我们得到的消息代码是这个范围的,SSH 会返回错误信息,并断开连接。例如如以下几种消息所对应的代码号:

SSH_MSG_USERAUTH_REQUEST 50:用户发送一个验证请求。

SSH_MSG_USERAUTH_FAILURE 51:用户验证请求失败。

SSH_MSG_USERAUTH_SUCCESS 52:用户验证请求成功。

那么对于不同的认证方式,又有其各自的消息代码。

在每次客户端发送请求的时候,服务器都会检查当前的 service name 和 username 是否合法,如果当前的 service name 或者 username 不可用,那么服务器端会立刻断掉请求连接。

下面来实现一个对 service name 验证的请求,发送数据格式如下:

byte SSH_MSG_SERVICE_REQUEST

string service name in US-ASCIII

具体代码如下:

清单 3. 类 AuthServiceRequest
 package com.my.test.ssh2.auth; import com.my.test.ssh2.common.ProcessTypes; public class AuthServiceRequest {    private String serviceName;    public AuthServiceRequest(String serviceName){        this.serviceName = serviceName;    }        /**     * 取得指定服务器名称的认证消息     * @return request – 返回一条十六进制消息     **/    public byte [] getRequestMessage() {        byte [] request;        ProcessTypes type = new ProcessTypes();        type.asByte(AuthConstant.SSH_MSG_SERVICE_REQUEST);        type.asString(serviceName);        request = type.getBytes();        return request;    }}

转换后发送的消息如下:

[5, 0, 0, 0, 12, 115, 115, 104, 45, 117, 115, 101, 114, 97, 117, 116, 104]

然后再对此进行算法加密,发送到服务器端。

当前协议使用的 service name 是”ssh-userauth”,如果客户端请求的不是这个 service name,那么服务器会报如下错误:

清单 4. service name 异常
 Caused by: java.io.IOException: Peer sent DISCONNECT message (reason code 2): bad service request demo-ssh-auth

如果客户端通过了 service name 的验证后,下一步我们就可以实现具体的认证方式了。流程图如下所示:

图 5. Authentication 类图

TransportManager 类是用来处理传输协议层的业务逻辑。在这里主要处理数据的解密、加密、压缩等操作,这些功能的具体实现主要通过 TransportControl 类来完成,TrasportControl 类会根据客户端和服务器端协商的数据算法来选择具体的算法如 sha-1、MD5 等。通过 TransportControl 类处理完的数据会存储到 Packets 类里,生成一个数据包的列表,为 AuthManager 类提供必要的数据信息。Connect 类是用来处理连接协议层的业务逻辑。主要用于得到一个远程机器的连接句柄、产生一个安全信道、对 TransportManager 类做数据初始化操作等。AuthManager 类是用来处理认证协议层的业务逻辑。主要是对不同登录认证方式的请求和从服务器端得到的请求回复做处理,通过客户端选择的不同的认证方式调用不同的认证方式实现类,比如 AuthRequestByPassword 类定义了通过密码认证方式的实现。

图 6. 认证协议流程图

首先,开启一个线程用来接收从服务器发送的加密数据包,然后对这个数据包做算法解密处理并放到一个模型化的堆栈 (Packet List),而另一个线程会监听当前的 Packet List 里是否有可用的数据包,并对其做解析处理包括对数据包是否合法、是否满足某种认证算法等。如果数据包所包含的认证方式和当前客户端请求的认证方式不匹配,那么,客户端就会失去服务器的连接。反之,如果客户端请求的认证方式包含在服务器开启的认证方式,客户端会返回给服务器一个成功请求,并建立连接会话。

none 认证方式

无认证方式(none authentication),这种认证方式通常是在第一次请求发送的时候使用的,因为通过这个认证方式,我们可以得到当前服务器端支持的所有认证方式的列表,通过这个列表我们就可以验证我们想要使用的认证方式是否被服务器端所支持。当然,如果远程目标机器支持这种 none 认证方式,那么客户端就直接得到了一个会话连接,但是这种认证方式是 SSH 协议里所不推荐使用的。

实现代码如下:

清单 5. 类 AuthRequestByNone
 package com.my.test.ssh2.auth; import com.my.test.ssh2.common.ProcessTypes; public class AuthRequestByNone {    private String userName;    private String serviceName;    public AuthRequestByNone(String serviceName, String user) {        this.serviceName = serviceName;        this.userName = user;    } /** * 取得指定服务器名称和用户名的认证消息* @return request - 返回一条十六进制消息* */    public byte [] getRequestMessage() {        byte [] request;        ProcessTypes type = new ProcessTypes();        type.asByte(AuthConstant.SSH_MSG_USERAUTH_REQUEST);        type.asString(userName);        type.asString(serviceName);        type.asString(AuthConstant.SSH_NONE_AUTHENTICATION_METHOD);        request = type.getBytes();        return request;    } }

从流程图 6 中可以看出,当我们发送一个 none 认证方式的时候,如果服务器不支持 none 认证,那么客户端就可以取得服务器端的认证方式列表。首先解析服务器端返回的包信息,例如:

[51, 0, 0, 0, 34, 112, 117, 98, 108, 105, 99, 107, 101, 121, 44, 103, 115, 115, 97, 112, 105, 45, 119, 105, 116, 104, 45, 109, 105, 99, 44, 112, 97, 115, 115, 119, 111, 114, 100, 0]

经过客户端的算法解析之后,我们可以得到含有如下信息的数据包:( 对于算法原理的具体说明是定义在传输层协议里,不是本文所讨论的范围。)

代码 51,说明用户验证请求失败。

从第 5 位到第 34 位记录了所当前服务器所支持的认证方法,解析后可得到 publickey, gssapi-with-mic, password 的一个由逗号隔开的认证方式字符串。

最后一位 0 表示,当前的请求失败,但并不是说整个的连接就断掉了。

解析数据包的具体算法如下:

清单 6. 解析数据算法
 ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) | (arr[pos++] & 0xff);

对于服务器端来说,它要对客户端的请求作一反馈,从而说明当前的请求是否成功。

数据格式如下:

 byte SSH_MSG_USERAUTH_FAILURE name-list authentications that can continue boolean partial success

所以,这就正好解释了上述,从服务器端得到的数据解析结果。

实现部分代码如下:

清单 7. 初始化函数
 public boolean initialize(String userName) throws IOException{                // 预处理服务名称的请求        AuthServiceRequest serviceRequest = new AuthServiceRequest(AuthConstant.SSH_SERVICE_NAME);        IManager transManager = ManagerFactory.getManager(Constant.TRANSPORT_LAYER); transManager.sendMessage(serviceRequest.getRequestMessage());                // 处理无认证方式的消息请求  AuthRequestByNone authNone = new AuthRequestByNone(AuthConstant.SSH_CONN_SERVICE_NAME,userName); transManager.sendMessage(authNone.getRequestMessage()); byte[] message = getMessage();        // 验证当前的服务名称是否合法        if(!isAccepted(message)){            return false;        }               // 取得无认证方式的请求数据包        message = getMessage();        // 验证当前的请求是否成功        if(isRequestFailed(message)){            return false;        }        return true; } private boolean isRequestFailed(byte [] messages) throws IOException {        if (messages[0] == AuthConstant.SSH_MSG_USERAUTH_SUCCESS){            return true;        }        if (messages[0] == AuthConstant.SSH_MSG_USERAUTH_FAILURE){            AuthFailure failure = new AuthFailure(messages);            authentications = failure.getAuthThatCanContinue();            isPartialSuccess = failure.isPartialSuccess();            return false;        }                throw new IOException("Unexpected SSH message (type " + messages[0] + ")"); }

当客户端得到了这个 authentications 数组之后,客户端就可以验证当前用户使用的远程登录认证方式是否是服务器所支持的。如果是那么再发送一条匹配的认证方式,从而返回登录认证成功,否则失败并打印出合理的错误信息。下面用 password 的认证方式为例做进一步说明。

password 认证方式

对于 password 认证方式来说,它的数据请求格式如下:

 byte SSH_MSG_USERAUTH_REQUEST string user name string service name string "password"boolean FALSE string plaintext password in ISO-10646 UTF-8 encoding

具体类的实现方式和 none 认证类的实现类似,只是 getRequestMessage() 方法所有不同。

实现代码:

清单 8. 生成请求数据函数
 public byte [] getRequestMessage() {     byte [] request;     ProcessTypes type = new ProcessTypes();     type.asByte(AuthConstant.SSH_MSG_USERAUTH_REQUEST);     type.asString(userName);     type.asString(serviceName);     type.asString(AuthConstant.SSH_PASSWORD_AUTHENTICATION_METHOD);     type.asString(password);     request = type.getBytes();     return request; }

在这里我们需要提供给服务器端一个用户口令,这个口令会在发送给服务器端之前被进行算法加密的处理。调用 password 认证方式的代码如下:

清单 9. password 认证函数
 public boolean passwordAuthentication(String user, String pass) throws IOException{ // 初始化请求        initialize(user);                // 验证指定的认证方式是否是 SSH 服务器所支持的if(verifyAuthenticatonMethods(AuthConstant.SSH_PASSWORD_AUTHENTICATION_METHOD)){ return false; } // 调用密码认证方式AuthRequestByPassword passwordRequest = new AuthRequestByPassword(AuthConstant.SSH_CONN_SERVICE_NAME,user,pass); // 发送一个消息请求到服务器端 IManager transManager = ManagerFactory.getManager(Constant.TRANSPORT_LAYER); transManager.sendMessage(passwordRequest.getRequestMessage()); // 从服务器端获取数据包byte[] message = getMessage(); // 验证当前的请求是否成功 if(isRequestFailed(message)){ return false; } return true; }

客户端首先会做初始化操作,包括数据加密算法的协商、得到服务器端支持的认证方式等。其次客户端会检查当前用户使用的登录认证方式是否合法,然后再发送一个请求给服务器端,告诉服务器当前使用 password 认证进行远程登录。最后,服务器会返回一个数据包,里面包含了对这个请求的回复,如果验证成功,那么连接就可以开启一个安全的会话了。至此,password 认证方式的解析就完成了,接下来用户就可以对远程机器做操作了,这部分的具体说明是在 SSH 的连接层协议里,不是本文的讨论范围。

回页首

总结

篇幅所限,本文就以 password 的认证方式为例进行了客户端远程登录的认证方式的讨论,对于其他认证方式会在以后的文章中讨论。在客户端用 SSH 协议进行远程登录的时候,提供了很多常见的认证方式,每种认证方式发送的数据包的数据结构略有不同,同时也提供了对外扩展接口,可以自定义认证方式。通过对本文的阅读,可以初步了解到,SSH 协议在用户认证层的基本原理,希望能对读者在以后的项目开发中,对 SSH 协议的使用有所帮助。

原文:http://www.ibm.com/developerworks/cn/java/j-lo-sshauthentication/

转载于:https://www.cnblogs.com/davidwang456/p/4051112.html

Java 实现 SSH 协议的客户端登录认证方式--转载相关推荐

  1. Java 实现 SSH 协议的客户端登录认证方式

    摘自:http://blog.sae.sina.com.cn/archives/333/comment-page-1#comment-37391 简介: 本文首先对 SSH 协议的基础知识作以介绍,然 ...

  2. 数据库oracle认证方式,oracle数据库启动流程及登录认证方式详解

    转自:https://www.2cto.com/database/201803/726644.html ■  oracle启动流程-windows下 1) lsnrctl start  (启动监听) ...

  3. 登录验证应该是进行在客户端还是服务器端_网站登录认证方式

    目前大部分软件系统资源访问都是使用HTTP协议,HTTP是无状态的协议,每次请求默认都是相互独立的.但是大部分情况下我们需要记录请求资源的用户信息,也就是保存会话,从而对资源的访问做限制,这是我们认证 ...

  4. java web 项目如何获取客户端登录帐号信息(用于SSO或其他)

    前言 在java 中可以通过System 获取操作系统的相关信息. 类似: String sys_user_name = System.getProperty("user.name" ...

  5. cmd下载远程linux的文件,Java利用ssh协议实现从远程Linux服务器下载文件和文件夹...

    近来应需求需要,做了服务器之间的文件传输,利用的ssh协议.查阅各种资料博客,基本都是下载文件的方法,找不到下载文件夹得方法.思索多日写了一个下载文件夹的简单方法,可能步骤繁琐,优化不大好.由于jsc ...

  6. 全网疯传!Java利用TCP协议实现客户端与服务器通信【附通信源码

    目录 TCP协议概念 ServerSocket类 服务器端程序 客户端程序 Hello!大家好哇!我是灰小猿! 上一篇博客和大家分享了在网络编程中要注意的基础知识,关于IP.TCP.UDP以及端口和套 ...

  7. Java的FTP协议级客户端实现详解

    10.2.4.3 例子3:网络应用层协议的开发 清华大学出版社<Java程序员,上班那点事儿>作者:钟声--第10章<高手有多高菜鸟有多菜>部分节选.        大家也许都 ...

  8. 个人博客系列【Hexo】——git 使用ssh协议免密登录

    目录 一.环境准备 二.问题描述 三.验证过程 四.解决方法 1.设置用户信息 2.配置SSH key 1)右键打开菜单 点击Git Bash Here 2)创建ssh key ​3.复制key 4. ...

  9. java通过ldap添加用户后_ldap连接不上改用户_JAVA通过LDAP做用户登录认证,怎么做业务的异常处理?...

    通过java.namming包实现LDAP用户登录认证,怎么区分账号被冻结.停用.不存在等异常的业务情况呢? 参考的是:网上普遍流传的LDAP连接的代码 public boolean auth(Str ...

最新文章

  1. 【转】matlab函数_连通区域
  2. math python 向上取整_Python的数值基本运算和其它数学运算方法
  3. 极端懒惰:使用Spring Boot开发JAX-RS服务
  4. flask+uwsgi+supervisor+nginx在局域网服务器上部署实践
  5. python面试题No6
  6. nvm、npm、nodejs的关系
  7. jmeter 使用命令行执行
  8. 模仿百思不得姐项目笔记
  9. layui时间日期控件laydate设置默认值,并且结束时间大于开始时间
  10. 2015年动漫电影观影指南值得收藏
  11. 【备忘录】浏览器不能上网但是微信能收到消息的一个解决办法
  12. 还有什么软件可以测试苹果真假,哪个软件可以检测苹果6s手机的真假
  13. C. Alice and the Cake
  14. 江民KV2007离线包和安装包下载
  15. 世界气象日话“海洋气象”系列之——CMRC
  16. 【PTA】【C语言】书香节
  17. 在数据库中如何新增一个字段?
  18. jenkins安装配置
  19. 牛客练习赛51 C 勾股定理 (结论题)
  20. python新手爬虫练习(二)-编码

热门文章

  1. boot spring 接口接收数据_基于 Spring Boot 实现 Restful 风格接口,实现增删改查功能...
  2. linux 设备驱动 百度,Linux设备驱动之input子系统
  3. linux 踢出其他用户,Linux查看当前登录用户并踢出用户
  4. html支持的语音文件格式,html5中如何设置audio支持音频格式
  5. 关于不过洋节的通知_蟠桃宫小学关于平安夜、圣诞节安全教育告家长通知书
  6. android studio项目总结,android studio 3.0 升级 项目遇到的问题及更改思路(问题小结)...
  7. Java:抽象方法和抽象类,抽象类应用模板方法模式,接口及使用
  8. Java:封装的概念,UML类图,构造方法,this关键字,static关键字,方法重载,包package
  9. yolov3-tf2 数据格式压缩
  10. svd 分解详细证明