🔨 配置 vnc 资产.

This commit is contained in:
lijiahangmax
2025-07-04 17:36:45 +08:00
parent 6fa2d65e18
commit a546827432
13 changed files with 573 additions and 28 deletions

View File

@@ -24,6 +24,7 @@ package org.dromara.visor.module.asset.api;
import org.dromara.visor.common.session.config.RdpConnectConfig;
import org.dromara.visor.common.session.config.SshConnectConfig;
import org.dromara.visor.common.session.config.VncConnectConfig;
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
/**
@@ -87,4 +88,30 @@ public interface HostConnectApi {
*/
RdpConnectConfig getRdpConnectConfig(HostDTO host, Long userId);
/**
* 获取 VNC 连接配置
*
* @param hostId hostId
* @return session
*/
VncConnectConfig getVncConnectConfig(Long hostId);
/**
* 使用用户配置获取 VNC 连接配置
*
* @param hostId hostId
* @param userId userId
* @return session
*/
VncConnectConfig getVncConnectConfig(Long hostId, Long userId);
/**
* 使用用户配置获取 VNC 连接配置
*
* @param host host
* @param userId userId
* @return session
*/
VncConnectConfig getVncConnectConfig(HostDTO host, Long userId);
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.entity.dto.host;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
import org.dromara.visor.common.security.UpdatePasswordAction;
import javax.validation.constraints.*;
/**
* 主机 VNC 配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/9/13 16:18
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "HostVncConfigDTO", description = "主机 VNC 配置业务对象")
public class HostVncConfigDTO implements GenericsDataModel, UpdatePasswordAction {
@NotNull
@Min(value = 1)
@Max(value = 65535)
@Schema(description = "主机端口")
private Integer port;
@Size(max = 128)
@Schema(description = "用户名")
private String username;
@NotBlank
@Size(max = 12)
@Schema(description = "认证方式")
private String authType;
@Schema(description = "密码")
private String password;
@Schema(description = "身份id")
private Long identityId;
@Schema(description = "无用户名")
private Boolean noUsername;
@Schema(description = "无密码")
private Boolean noPassword;
@Schema(description = "时区")
private String timezone;
@Schema(description = "剪切板编码")
private String clipboardEncoding;
@Schema(description = "是否使用新密码 仅参数")
private Boolean useNewPassword;
@Schema(description = "是否已设置密码 仅返回")
private Boolean hasPassword;
}

View File

@@ -27,6 +27,7 @@ import lombok.AllArgsConstructor;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.module.asset.entity.dto.host.HostRdpConfigDTO;
import org.dromara.visor.module.asset.entity.dto.host.HostSshConfigDTO;
import org.dromara.visor.module.asset.entity.dto.host.HostVncConfigDTO;
import java.util.ArrayList;
import java.util.Arrays;
@@ -54,6 +55,11 @@ public enum HostTypeEnum {
*/
RDP(HostRdpConfigDTO.class),
/**
* VNC
*/
VNC(HostVncConfigDTO.class),
;
private final Class<?> clazz;

View File

@@ -25,6 +25,7 @@ package org.dromara.visor.module.asset.api.impl;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.session.config.RdpConnectConfig;
import org.dromara.visor.common.session.config.SshConnectConfig;
import org.dromara.visor.common.session.config.VncConnectConfig;
import org.dromara.visor.module.asset.api.HostConnectApi;
import org.dromara.visor.module.asset.convert.HostProviderConvert;
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
@@ -77,4 +78,19 @@ public class HostConnectApiImpl implements HostConnectApi {
return hostConnectService.getRdpConnectConfig(HostProviderConvert.MAPPER.to(host), userId);
}
@Override
public VncConnectConfig getVncConnectConfig(Long hostId) {
return hostConnectService.getVncConnectConfig(hostId);
}
@Override
public VncConnectConfig getVncConnectConfig(Long hostId, Long userId) {
return hostConnectService.getVncConnectConfig(hostId, userId);
}
@Override
public VncConnectConfig getVncConnectConfig(HostDTO host, Long userId) {
return hostConnectService.getVncConnectConfig(HostProviderConvert.MAPPER.to(host), userId);
}
}

View File

@@ -49,6 +49,11 @@ public enum HostConfigStrategyEnum implements GenericsStrategyDefinition {
*/
RDP(HostRdpConfigStrategy.class),
/**
* VNC
*/
VNC(HostVncConfigStrategy.class),
;
private final Class<? extends GenericsDataStrategy<? extends GenericsDataModel>> strategyClass;

View File

@@ -0,0 +1,116 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.config;
import cn.orionsec.kit.lang.utils.Booleans;
import cn.orionsec.kit.lang.utils.Strings;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.module.asset.dao.HostIdentityDAO;
import org.dromara.visor.module.asset.entity.domain.HostIdentityDO;
import org.dromara.visor.module.asset.entity.dto.host.HostVncConfigDTO;
import org.dromara.visor.module.asset.enums.HostAuthTypeEnum;
import org.dromara.visor.module.asset.enums.HostIdentityTypeEnum;
import org.dromara.visor.module.asset.enums.HostTypeEnum;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 主机 VNC 配置策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/9/19 14:26
*/
@Component
public class HostVncConfigStrategy extends AbstractHostConfigStrategy<HostVncConfigDTO> {
@Resource
private HostIdentityDAO hostIdentityDAO;
public HostVncConfigStrategy() {
super(HostVncConfigDTO.class);
}
@Override
public HostVncConfigDTO getDefault() {
return HostVncConfigDTO.builder()
.port(5900)
.username(Const.ROOT)
.authType(HostAuthTypeEnum.PASSWORD.name())
.noUsername(false)
.noPassword(false)
.clipboardEncoding(Const.UTF_8)
.build();
}
@Override
protected void preValid(HostVncConfigDTO model) {
// 验证剪切板编码格式
String clipboardEncoding = model.getClipboardEncoding();
if (!Strings.isBlank(clipboardEncoding)) {
this.validCharset(clipboardEncoding);
}
// 检查主机身份是否存在
Long identityId = model.getIdentityId();
if (identityId != null) {
HostIdentityDO identity = Valid.notNull(hostIdentityDAO.selectById(identityId), ErrorMessage.IDENTITY_ABSENT);
Valid.eq(HostIdentityTypeEnum.PASSWORD.name(), identity.getType(), ErrorMessage.CHECK_IDENTITY_PASSWORD);
}
}
@Override
protected void valid(HostVncConfigDTO model) {
// 验证填充后的参数
Valid.valid(model);
}
@Override
protected void updateFill(HostVncConfigDTO beforeModel, HostVncConfigDTO afterModel) {
// 无密码设置认证方式为密码验证
if (Booleans.isTrue(afterModel.getNoPassword())) {
afterModel.setAuthType(HostAuthTypeEnum.PASSWORD.name());
}
// 加密密码
this.checkEncryptPassword(afterModel.getAuthType(), beforeModel, afterModel);
afterModel.setHasPassword(null);
afterModel.setUseNewPassword(null);
}
@Override
public HostVncConfigDTO parse(String serialModel) {
return HostTypeEnum.VNC.parse(serialModel);
}
@Override
public void toView(HostVncConfigDTO model) {
if (model == null) {
return;
}
model.setHasPassword(Strings.isNotBlank(model.getPassword()));
model.setPassword(null);
}
}

View File

@@ -27,10 +27,7 @@ import lombok.Getter;
import org.dromara.visor.common.handler.data.GenericsStrategyDefinition;
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
import org.dromara.visor.common.handler.data.strategy.GenericsDataStrategy;
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostLabelExtraStrategy;
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostRdpExtraStrategy;
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostSpecExtraStrategy;
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostSshExtraStrategy;
import org.dromara.visor.module.asset.handler.host.extra.strategy.*;
/**
* 主机额外配置项策略枚举
@@ -58,6 +55,11 @@ public enum HostExtraItemEnum implements GenericsStrategyDefinition {
*/
RDP(HostRdpExtraStrategy.class, true),
/**
* VNC 额外配置
*/
VNC(HostVncExtraStrategy.class, true),
/**
* 规格信息配置
*/

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.extra.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
/**
* 主机拓展信息 - vnc 模型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/20 21:36
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HostVncExtraModel implements GenericsDataModel {
/**
* 端口号
*/
private Integer port;
/**
* 低带宽模式
*/
private Boolean lowBandwidthMode;
}

View File

@@ -59,6 +59,7 @@ public class HostRdpExtraStrategy extends AbstractGenericsDataStrategy<HostRdpEx
public HostRdpExtraModel getDefault() {
return HostRdpExtraModel.builder()
.authType(HostExtraAuthTypeEnum.DEFAULT.name())
.lowBandwidthMode(false)
.build();
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.extra.strategy;
import org.dromara.visor.common.handler.data.strategy.AbstractGenericsDataStrategy;
import org.dromara.visor.module.asset.handler.host.extra.model.HostVncExtraModel;
import org.springframework.stereotype.Component;
/**
* 主机拓展信息 - rdp 模型处理策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/20 22:17
*/
@Component
public class HostVncExtraStrategy extends AbstractGenericsDataStrategy<HostVncExtraModel> {
public HostVncExtraStrategy() {
super(HostVncExtraModel.class);
}
@Override
public HostVncExtraModel getDefault() {
return HostVncExtraModel.builder()
.lowBandwidthMode(false)
.build();
}
}

View File

@@ -24,6 +24,7 @@ package org.dromara.visor.module.asset.service;
import org.dromara.visor.common.session.config.RdpConnectConfig;
import org.dromara.visor.common.session.config.SshConnectConfig;
import org.dromara.visor.common.session.config.VncConnectConfig;
import org.dromara.visor.module.asset.entity.domain.HostDO;
import org.dromara.visor.module.asset.entity.request.host.HostTestConnectRequest;
@@ -95,4 +96,30 @@ public interface HostConnectService {
*/
RdpConnectConfig getRdpConnectConfig(HostDO host, Long userId);
/**
* 获取 VNC 连接配置
*
* @param hostId hostId
* @return session
*/
VncConnectConfig getVncConnectConfig(Long hostId);
/**
* 使用用户配置获取 VNC 连接配置
*
* @param hostId hostId
* @param userId userId
* @return session
*/
VncConnectConfig getVncConnectConfig(Long hostId, Long userId);
/**
* 使用用户配置获取 VNC 连接配置
*
* @param host host
* @param userId userId
* @return session
*/
VncConnectConfig getVncConnectConfig(HostDO host, Long userId);
}

View File

@@ -22,6 +22,7 @@
*/
package org.dromara.visor.module.asset.service.impl;
import cn.orionsec.kit.lang.utils.Booleans;
import cn.orionsec.kit.lang.utils.Exceptions;
import cn.orionsec.kit.lang.utils.io.Streams;
import cn.orionsec.kit.net.host.SessionStore;
@@ -30,6 +31,7 @@ import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.session.config.BaseConnectConfig;
import org.dromara.visor.common.session.config.RdpConnectConfig;
import org.dromara.visor.common.session.config.SshConnectConfig;
import org.dromara.visor.common.session.config.VncConnectConfig;
import org.dromara.visor.common.session.ssh.SessionStores;
import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.module.asset.dao.HostDAO;
@@ -40,6 +42,7 @@ import org.dromara.visor.module.asset.entity.domain.HostIdentityDO;
import org.dromara.visor.module.asset.entity.domain.HostKeyDO;
import org.dromara.visor.module.asset.entity.dto.host.HostRdpConfigDTO;
import org.dromara.visor.module.asset.entity.dto.host.HostSshConfigDTO;
import org.dromara.visor.module.asset.entity.dto.host.HostVncConfigDTO;
import org.dromara.visor.module.asset.entity.request.host.HostTestConnectRequest;
import org.dromara.visor.module.asset.enums.HostAuthTypeEnum;
import org.dromara.visor.module.asset.enums.HostExtraAuthTypeEnum;
@@ -48,6 +51,7 @@ import org.dromara.visor.module.asset.enums.HostTypeEnum;
import org.dromara.visor.module.asset.handler.host.extra.HostExtraItemEnum;
import org.dromara.visor.module.asset.handler.host.extra.model.HostRdpExtraModel;
import org.dromara.visor.module.asset.handler.host.extra.model.HostSshExtraModel;
import org.dromara.visor.module.asset.handler.host.extra.model.HostVncExtraModel;
import org.dromara.visor.module.asset.service.AssetAuthorizedDataService;
import org.dromara.visor.module.asset.service.HostConfigService;
import org.dromara.visor.module.asset.service.HostConnectService;
@@ -189,6 +193,41 @@ public class HostConnectServiceImpl implements HostConnectService {
return this.getRdpConnectConfig(host, config, extra);
}
@Override
public VncConnectConfig getVncConnectConfig(Long hostId) {
log.info("HostConnectService.getVncConnectConfig-withHost hostId: {}", hostId);
// 查询主机
HostDO host = hostDAO.selectById(hostId);
// 查询主机配置
HostVncConfigDTO config = hostConfigService.getHostConfig(hostId, HostTypeEnum.VNC.name());
// 获取配置
return this.getVncConnectConfig(host, config, null);
}
@Override
public VncConnectConfig getVncConnectConfig(Long hostId, Long userId) {
// 查询主机
HostDO host = hostDAO.selectById(hostId);
Valid.notNull(host, ErrorMessage.HOST_ABSENT);
// 获取配置
return this.getVncConnectConfig(host, userId);
}
@Override
public VncConnectConfig getVncConnectConfig(HostDO host, Long userId) {
Long hostId = host.getId();
log.info("HostConnectService.getVncConnectConfig hostId: {}, userId: {}", hostId, userId);
// 验证权限
this.validHostAuthorized(userId, hostId);
// 获取主机配置
HostVncConfigDTO config = hostConfigService.getHostConfig(hostId, HostTypeEnum.VNC.name());
Valid.notNull(config, ErrorMessage.CONFIG_ABSENT);
// 查询主机额外配置
HostVncExtraModel extra = hostExtraService.getHostExtra(userId, hostId, HostExtraItemEnum.VNC);
// 获取连接配置
return this.getVncConnectConfig(host, config, extra);
}
/**
* 获取主机 SSH 连接配置
*
@@ -254,14 +293,7 @@ public class HostConnectServiceImpl implements HostConnectService {
connectConfig.setPassword(config.getPassword());
} else if (HostAuthTypeEnum.KEY.equals(authType)) {
// 密钥认证
Long keyId = config.getKeyId();
Valid.notNull(keyId, ErrorMessage.KEY_ABSENT);
HostKeyDO key = hostKeyDAO.selectById(keyId);
Valid.notNull(key, ErrorMessage.KEY_ABSENT);
connectConfig.setKeyId(keyId);
connectConfig.setPublicKey(key.getPublicKey());
connectConfig.setPrivateKey(key.getPrivateKey());
connectConfig.setPrivateKeyPassword(key.getPassword());
this.setSshKey(config.getKeyId(), connectConfig);
}
return connectConfig;
}
@@ -279,6 +311,8 @@ public class HostConnectServiceImpl implements HostConnectService {
// 填充认证信息
RdpConnectConfig connectConfig = RdpConnectConfig.builder()
.hostPort(config.getPort())
.username(config.getUsername())
.password(config.getPassword())
.versionGt81(config.getVersionGt81())
.timezone(config.getTimezone())
.keyboardLayout(config.getKeyboardLayout())
@@ -304,25 +338,53 @@ public class HostConnectServiceImpl implements HostConnectService {
config.setIdentityId(extra.getIdentityId());
}
}
// 身份认证
HostAuthTypeEnum authType = HostAuthTypeEnum.of(config.getAuthType());
if (HostAuthTypeEnum.IDENTITY.equals(authType)) {
// 身份认证 - 仅密码
authType = HostAuthTypeEnum.PASSWORD;
Valid.notNull(config.getIdentityId(), ErrorMessage.IDENTITY_ABSENT);
HostIdentityDO identity = hostIdentityDAO.selectById(config.getIdentityId());
Valid.notNull(identity, ErrorMessage.IDENTITY_ABSENT);
// 设置身份信息
config.setUsername(identity.getUsername());
config.setPassword(identity.getPassword());
// 填充身份认证信息
if (HostAuthTypeEnum.IDENTITY.name().equals(config.getAuthType())) {
this.setIdentityPasswordAuthorization(config.getIdentityId(), connectConfig);
}
return connectConfig;
}
/**
* 获取 VNC 连接信息
*
* @param host host
* @param config config
* @return info
*/
private VncConnectConfig getVncConnectConfig(HostDO host,
HostVncConfigDTO config,
HostVncExtraModel extra) {
// 填充认证信息
connectConfig.setUsername(config.getUsername());
if (HostAuthTypeEnum.PASSWORD.equals(authType)) {
// 密码认证
connectConfig.setPassword(config.getPassword());
VncConnectConfig connectConfig = VncConnectConfig.builder()
.hostPort(config.getPort())
.username(config.getUsername())
.password(config.getPassword())
.timezone(config.getTimezone())
.clipboardEncoding(config.getClipboardEncoding())
.build();
// 填充基础主机信息
this.setBaseConnectConfig(connectConfig, host);
if (extra != null) {
// 设置额外配置信息
connectConfig.setLowBandwidthMode(extra.getLowBandwidthMode());
// 设置自定义端口
Integer extraPort = extra.getPort();
if (extraPort != null) {
connectConfig.setHostPort(extraPort);
}
}
// 填充身份认证信息
if (HostAuthTypeEnum.IDENTITY.name().equals(config.getAuthType())) {
this.setIdentityPasswordAuthorization(config.getIdentityId(), connectConfig);
}
// 无用户名
if (Booleans.isTrue(config.getNoUsername())) {
connectConfig.setUsername(null);
}
// 无密码
if (Booleans.isTrue(config.getNoPassword())) {
connectConfig.setPassword(null);
}
return connectConfig;
}
@@ -366,6 +428,41 @@ public class HostConnectServiceImpl implements HostConnectService {
}
}
/**
* 设置密码认证
*
* @param identityId identityId
* @param connectConfig connectConfig
*/
private void setIdentityPasswordAuthorization(Long identityId, BaseConnectConfig connectConfig) {
if (identityId == null) {
return;
}
// 查询身份信息
HostIdentityDO identity = hostIdentityDAO.selectById(identityId);
Valid.notNull(identity, ErrorMessage.IDENTITY_ABSENT);
// 设置身份信息
connectConfig.setUsername(identity.getUsername());
connectConfig.setPassword(identity.getPassword());
}
/**
* 设置 SSH 密钥信息
*
* @param keyId keyId
* @param connectConfig connectConfig
*/
private void setSshKey(Long keyId, SshConnectConfig connectConfig) {
Valid.notNull(keyId, ErrorMessage.KEY_ABSENT);
// 查询密钥信息
HostKeyDO key = hostKeyDAO.selectById(keyId);
Valid.notNull(key, ErrorMessage.KEY_ABSENT);
connectConfig.setKeyId(keyId);
connectConfig.setPublicKey(key.getPublicKey());
connectConfig.setPrivateKey(key.getPrivateKey());
connectConfig.setPrivateKeyPassword(key.getPassword());
}
/**
* 设置基础主机信息
*