增加服务器管理认证方式,增加测试连接接口
This commit is contained in:
parent
9868a32bc7
commit
0594d1856d
@ -177,6 +177,13 @@
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- SSH Client (SSHJ) -->
|
||||
<dependency>
|
||||
<groupId>com.hierynomus</groupId>
|
||||
<artifactId>sshj</artifactId>
|
||||
<version>0.38.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
|
||||
@ -41,6 +41,15 @@ public class ServerApiController
|
||||
return Response.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "测试SSH连接", description = "测试服务器SSH连接是否正常")
|
||||
@PostMapping("/{id}/test-connection")
|
||||
public Response<Boolean> testConnection(
|
||||
@Parameter(description = "服务器ID", required = true) @PathVariable Long id
|
||||
) {
|
||||
boolean success = serverService.testConnection(id);
|
||||
return Response.success(success);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void exportData(HttpServletResponse response, List<ServerDTO> data) {
|
||||
log.info("导出服务器数据,数据量:{}", data.size());
|
||||
|
||||
@ -19,5 +19,13 @@ public interface IServerService extends IBaseService<Server, ServerDTO, ServerQu
|
||||
* @return 更新后的服务器信息
|
||||
*/
|
||||
ServerDTO initializeServerInfo(Long serverId, ServerInitializeDTO dto);
|
||||
|
||||
/**
|
||||
* 测试服务器SSH连接
|
||||
*
|
||||
* @param serverId 服务器ID
|
||||
* @return 是否连接成功
|
||||
*/
|
||||
boolean testConnection(Long serverId);
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ package com.qqchen.deploy.backend.deploy.service.impl;
|
||||
import com.qqchen.deploy.backend.deploy.dto.ServerDTO;
|
||||
import com.qqchen.deploy.backend.deploy.dto.ServerInitializeDTO;
|
||||
import com.qqchen.deploy.backend.deploy.entity.Server;
|
||||
import com.qqchen.deploy.backend.deploy.enums.AuthTypeEnum;
|
||||
import com.qqchen.deploy.backend.deploy.enums.ServerStatusEnum;
|
||||
import com.qqchen.deploy.backend.deploy.query.ServerQuery;
|
||||
import com.qqchen.deploy.backend.deploy.repository.IServerRepository;
|
||||
@ -12,10 +13,15 @@ import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
/**
|
||||
@ -57,5 +63,93 @@ public class ServerServiceImpl
|
||||
|
||||
return converter.toDto(updated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testConnection(Long serverId) {
|
||||
// 1. 查询服务器信息
|
||||
Server server = serverRepository.findById(serverId)
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND));
|
||||
|
||||
// 2. 验证必要字段
|
||||
if (server.getHostIp() == null || server.getSshUser() == null) {
|
||||
throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"服务器IP和SSH用户名不能为空"});
|
||||
}
|
||||
|
||||
SSHClient ssh = new SSHClient();
|
||||
try {
|
||||
// 3. 配置SSH客户端
|
||||
// TODO: 【安全改进】生产环境应使用更安全的主机密钥验证方式
|
||||
// 当前:使用 PromiscuousVerifier 跳过主机密钥验证(不验证服务器身份,存在中间人攻击风险)
|
||||
// 建议改为:
|
||||
// 1. ssh.loadKnownHosts() - 使用 ~/.ssh/known_hosts 文件验证
|
||||
// 2. ssh.addHostKeyVerifier(new ConsoleVerifyingHostKeyVerifier()) - 首次自动接受,后续验证
|
||||
// 3. 在数据库中存储服务器指纹,首次连接时记录,后续验证
|
||||
ssh.addHostKeyVerifier(new PromiscuousVerifier());
|
||||
ssh.setTimeout(10000); // 10秒超时
|
||||
ssh.setConnectTimeout(10000);
|
||||
|
||||
// 4. 连接服务器
|
||||
int port = server.getSshPort() != null ? server.getSshPort() : 22;
|
||||
log.info("尝试连接服务器: {}@{}:{}", server.getSshUser(), server.getHostIp(), port);
|
||||
ssh.connect(server.getHostIp(), port);
|
||||
|
||||
// 5. 根据认证方式进行认证
|
||||
if (server.getAuthType() == AuthTypeEnum.KEY) {
|
||||
// 密钥认证
|
||||
if (server.getSshPrivateKey() == null || server.getSshPrivateKey().isEmpty()) {
|
||||
throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"SSH私钥不能为空"});
|
||||
}
|
||||
|
||||
KeyProvider keyProvider;
|
||||
if (server.getSshPassphrase() != null && !server.getSshPassphrase().isEmpty()) {
|
||||
// 私钥有密码保护
|
||||
keyProvider = ssh.loadKeys(server.getSshPrivateKey(), null,
|
||||
net.schmizz.sshj.userauth.password.PasswordUtils.createOneOff(server.getSshPassphrase().toCharArray()));
|
||||
} else {
|
||||
// 私钥无密码保护
|
||||
keyProvider = ssh.loadKeys(server.getSshPrivateKey(), null, null);
|
||||
}
|
||||
|
||||
ssh.authPublickey(server.getSshUser(), keyProvider);
|
||||
log.info("使用密钥认证成功");
|
||||
} else {
|
||||
// 密码认证
|
||||
if (server.getSshPassword() == null || server.getSshPassword().isEmpty()) {
|
||||
throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"SSH密码不能为空"});
|
||||
}
|
||||
|
||||
ssh.authPassword(server.getSshUser(), server.getSshPassword());
|
||||
log.info("使用密码认证成功");
|
||||
}
|
||||
|
||||
// 6. 测试执行简单命令
|
||||
try (var session = ssh.startSession()) {
|
||||
session.allocateDefaultPTY();
|
||||
var cmd = session.exec("echo 'Connection test successful'");
|
||||
cmd.join(5, TimeUnit.SECONDS);
|
||||
|
||||
if (cmd.getExitStatus() == 0) {
|
||||
log.info("SSH连接测试成功: {}", server.getHostIp());
|
||||
return true;
|
||||
} else {
|
||||
log.warn("SSH连接测试失败,命令执行异常: {}", server.getHostIp());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("SSH连接测试失败: {} - {}", server.getHostIp(), e.getMessage());
|
||||
throw new BusinessException(ResponseCode.ERROR, new Object[]{"SSH连接失败: " + e.getMessage()});
|
||||
} finally {
|
||||
// 7. 关闭连接
|
||||
try {
|
||||
if (ssh.isConnected()) {
|
||||
ssh.disconnect();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("关闭SSH连接失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user