feat: 主机终端访问.

This commit is contained in:
lijiahang
2023-12-28 19:18:46 +08:00
parent 638fbb9613
commit a1e372148a
22 changed files with 254 additions and 308 deletions

View File

@@ -0,0 +1,39 @@
package com.orion.ops.module.asset.config;
import com.orion.ops.module.asset.handler.host.terminal.TerminalDispatchHandler;
import com.orion.ops.module.asset.interceptor.TerminalInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import javax.annotation.Resource;
/**
* 资产模块 websocket 配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/28 11:39
*/
@Configuration
public class AssetWebSocketConfiguration implements WebSocketConfigurer {
@Value("${orion.websocket.prefix}")
private String prefix;
@Resource
private TerminalInterceptor terminalInterceptor;
@Resource
private TerminalDispatchHandler terminalDispatchHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 终端
registry.addHandler(terminalDispatchHandler, prefix + "/host/terminal/{token}")
.addInterceptors(terminalInterceptor)
.setAllowedOrigins("*");
}
}

View File

@@ -1,11 +1,6 @@
### 获取主机终端连接 token
POST {{baseUrl}}/asset/host-terminal/access
Content-Type: application/json
GET {{baseUrl}}/asset/host-terminal/access
Authorization: {{token}}
{
"hostId": 1
}
###

View File

@@ -1,18 +1,14 @@
package com.orion.ops.module.asset.controller;
import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.asset.define.operator.HostTerminalOperatorType;
import com.orion.ops.module.asset.entity.request.host.HostTerminalConnectRequest;
import com.orion.ops.module.asset.service.HostTerminalService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -37,12 +33,11 @@ public class HostTerminalController {
@Resource
private HostTerminalService hostTerminalService;
@OperatorLog(HostTerminalOperatorType.ACCESS)
@PostMapping("/access")
@Operation(summary = "获取主机终端连接 token")
@GetMapping("/access")
@Operation(summary = "获取主机终端 accessToken")
@PreAuthorize("@ss.hasPermission('asset:host-terminal:access')")
public String getHostAccessToken(@Validated @RequestBody HostTerminalConnectRequest request) {
return hostTerminalService.getHostAccessToken(request.getHostId(), SecurityUtils.getLoginUserId());
public String getHostTerminalAccessToken() {
return hostTerminalService.getHostTerminalAccessToken(SecurityUtils.getLoginUserId());
}
}

View File

@@ -1,7 +1,7 @@
package com.orion.ops.module.asset.convert;
import com.orion.ops.module.asset.entity.domain.HostConnectLogDO;
import com.orion.ops.module.asset.entity.dto.HostSshConnectDTO;
import com.orion.ops.module.asset.entity.dto.HostTerminalConnectDTO;
import com.orion.ops.module.asset.entity.request.host.HostConnectLogCreateRequest;
import com.orion.ops.module.asset.entity.request.host.HostConnectLogQueryRequest;
import com.orion.ops.module.asset.entity.vo.HostConnectLogVO;
@@ -28,7 +28,7 @@ public interface HostConnectLogConvert {
HostConnectLogVO to(HostConnectLogDO domain);
HostConnectLogCreateRequest to(HostSshConnectDTO dto);
HostConnectLogCreateRequest to(HostTerminalConnectDTO dto);
List<HostConnectLogVO> to(List<HostConnectLogDO> list);

View File

@@ -3,7 +3,7 @@ package com.orion.ops.module.asset.define.cache;
import com.orion.lang.define.cache.key.CacheKeyBuilder;
import com.orion.lang.define.cache.key.CacheKeyDefine;
import com.orion.lang.define.cache.key.struct.RedisCacheStruct;
import com.orion.ops.module.asset.entity.dto.HostSshConnectDTO;
import com.orion.ops.module.asset.entity.dto.HostTerminalAccessDTO;
import java.util.concurrent.TimeUnit;
@@ -16,10 +16,10 @@ import java.util.concurrent.TimeUnit;
*/
public interface HostTerminalCacheKeyDefine {
CacheKeyDefine HOST_TERMINAL_CONNECT = new CacheKeyBuilder()
.key("host:terminal:connect:{}")
.desc("主机终端连接信息 ${token}")
.type(HostSshConnectDTO.class)
CacheKeyDefine HOST_TERMINAL_ACCESS = new CacheKeyBuilder()
.key("host:terminal:access:{}")
.desc("主机终端访问token ${token}")
.type(HostTerminalAccessDTO.class)
.struct(RedisCacheStruct.STRING)
.timeout(3, TimeUnit.MINUTES)
.build();

View File

@@ -16,12 +16,12 @@ import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLe
@Module("asset:host-terminal")
public class HostTerminalOperatorType extends InitializingOperatorTypes {
public static final String ACCESS = "host-terminal:access";
public static final String CONNECT = "host-terminal:connect";
@Override
public OperatorType[] types() {
return new OperatorType[]{
new OperatorType(L, ACCESS, "连接主机终端 <sb>${hostName}</sb>"),
new OperatorType(L, CONNECT, "连接主机终端 <sb>${hostName}</sb>"),
};
}

View File

@@ -0,0 +1,31 @@
package com.orion.ops.module.asset.entity.dto;
import com.orion.ops.framework.desensitize.core.annotation.DesensitizeObject;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 主机终端访问参数
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/26 15:47
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DesensitizeObject
@Schema(name = "HostTerminalAccessDTO", description = "主机终端访问参数")
public class HostTerminalAccessDTO {
@Schema(description = "userId")
private Long userId;
@Schema(description = "token")
private String token;
}

View File

@@ -9,7 +9,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 主机连接参数
* 主机终端连接参数
*
* @author Jiahang Li
* @version 1.0.0
@@ -20,8 +20,8 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@DesensitizeObject
@Schema(name = "HostSshConnectDTO", description = "主机连接参数")
public class HostSshConnectDTO {
@Schema(name = "HostTerminalConnectDTO", description = "主机终端连接参数")
public class HostTerminalConnectDTO {
@Schema(description = "token")
private String token;

View File

@@ -1,30 +0,0 @@
package com.orion.ops.module.asset.entity.request.host;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* 主机终端连接 请求对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-20 11:55
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "HostTerminalConnectRequest", description = "主机终端连接 请求对象")
public class HostTerminalConnectRequest implements Serializable {
@NotNull
@Schema(description = "hostId")
private Long hostId;
}

View File

@@ -0,0 +1,52 @@
package com.orion.ops.module.asset.handler.host.terminal;
import com.orion.ops.framework.biz.operator.log.core.service.OperatorLogFrameworkService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import javax.annotation.Resource;
/**
* 终端处理器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/28 14:33
*/
@Slf4j
@Component
public class TerminalDispatchHandler implements WebSocketHandler {
@Resource
private OperatorLogFrameworkService operatorLogFrameworkService;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
log.info("afterConnectionEstablished");
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
log.info("handleMessage");
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
log.info("handleTransportError");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
log.info("afterConnectionClosed");
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}

View File

@@ -1,63 +1,57 @@
package com.orion.ops.module.asset.interceptor;
import com.orion.ops.framework.websocket.core.interceptor.UserHandshakeInterceptor;
import org.springframework.context.annotation.Configuration;
import com.orion.lang.utils.Urls;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel;
import com.orion.ops.framework.common.constant.ExtraFieldConst;
import com.orion.ops.framework.common.entity.RequestIdentity;
import com.orion.ops.framework.common.utils.Requests;
import com.orion.ops.module.asset.entity.dto.HostTerminalAccessDTO;
import com.orion.ops.module.asset.service.HostTerminalService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import javax.annotation.Resource;
import java.util.Map;
/**
* 终端拦截器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/27 23:53
*/
@Configuration
public class TerminalInterceptor implements WebSocketConfigurer {
// https://blog.csdn.net/oNew_Lifeo/article/details/130003676
// https://wstool.js.org/
@Slf4j
@Component
public class TerminalInterceptor implements HandshakeInterceptor {
@Resource
private UserHandshakeInterceptor userHandshakeInterceptor;
private HostTerminalService hostTerminalService;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new WebSocketHandler1(), "/orion/keep-alive/host/terminal")
.addInterceptors(userHandshakeInterceptor)
.setAllowedOrigins("*");
System.out.println("123");
}
static class WebSocketHandler1 implements WebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println(1);
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
System.out.println(message);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.out.println(1);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
System.out.println(1);
}
@Override
public boolean supportsPartialMessages() {
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// 获取 token
String token = Urls.getUrlSource(request.getURI().getPath());
log.info("TerminalInterceptor-beforeHandshake start token: {}", token);
// 获取连接数据
HostTerminalAccessDTO access = hostTerminalService.getAccessInfoByToken(token);
if (access == null) {
log.error("TerminalInterceptor-beforeHandshake absent token: {}", token);
return false;
}
// 设置参数
attributes.put(ExtraFieldConst.USER_ID, access.getUserId());
OperatorLogModel identity = new OperatorLogModel();
Requests.fillIdentity(identity);
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
log.info("afterHandshake");
}
}

View File

@@ -1,7 +1,8 @@
package com.orion.ops.module.asset.service;
import com.orion.net.host.SessionStore;
import com.orion.ops.module.asset.entity.dto.HostSshConnectDTO;
import com.orion.ops.module.asset.entity.dto.HostTerminalAccessDTO;
import com.orion.ops.module.asset.entity.dto.HostTerminalConnectDTO;
/**
* 主机终端服务
@@ -13,21 +14,29 @@ import com.orion.ops.module.asset.entity.dto.HostSshConnectDTO;
public interface HostTerminalService {
/**
* 获取主机终端连接 token
* 获取主机终端访问 accessToken
*
* @param userId userId
* @return session
*/
String getHostTerminalAccessToken(Long userId);
/**
* 通过 accessToken 获取主机终端访问信息
*
* @param token token
* @return config
*/
HostTerminalAccessDTO getAccessInfoByToken(String token);
/**
* 使用用户配置打开获取连接信息
*
* @param hostId hostId
* @param userId userId
* @return session
*/
String getHostAccessToken(Long hostId, Long userId);
/**
* 通过 token 获取主机终端连接信息
*
* @param token token
* @return config
*/
HostSshConnectDTO getConnectInfoByToken(String token);
HostTerminalConnectDTO getTerminalConnectInfo(Long userId, Long hostId);
/**
* 使用默认配置打开主机会话
@@ -43,6 +52,6 @@ public interface HostTerminalService {
* @param conn conn
* @return session
*/
SessionStore openSessionStore(HostSshConnectDTO conn);
SessionStore openSessionStore(HostTerminalConnectDTO conn);
}

View File

@@ -6,13 +6,11 @@ import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Strings;
import com.orion.net.host.SessionHolder;
import com.orion.net.host.SessionStore;
import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogs;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.constant.ErrorMessage;
import com.orion.ops.framework.common.utils.CryptoUtils;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.framework.redis.core.utils.RedisStrings;
import com.orion.ops.module.asset.convert.HostConnectLogConvert;
import com.orion.ops.module.asset.dao.HostDAO;
import com.orion.ops.module.asset.dao.HostIdentityDAO;
import com.orion.ops.module.asset.dao.HostKeyDAO;
@@ -20,9 +18,12 @@ import com.orion.ops.module.asset.define.cache.HostTerminalCacheKeyDefine;
import com.orion.ops.module.asset.entity.domain.HostDO;
import com.orion.ops.module.asset.entity.domain.HostIdentityDO;
import com.orion.ops.module.asset.entity.domain.HostKeyDO;
import com.orion.ops.module.asset.entity.dto.HostSshConnectDTO;
import com.orion.ops.module.asset.entity.request.host.HostConnectLogCreateRequest;
import com.orion.ops.module.asset.enums.*;
import com.orion.ops.module.asset.entity.dto.HostTerminalAccessDTO;
import com.orion.ops.module.asset.entity.dto.HostTerminalConnectDTO;
import com.orion.ops.module.asset.enums.HostConfigTypeEnum;
import com.orion.ops.module.asset.enums.HostExtraItemEnum;
import com.orion.ops.module.asset.enums.HostExtraSshAuthTypeEnum;
import com.orion.ops.module.asset.enums.HostSshAuthTypeEnum;
import com.orion.ops.module.asset.handler.host.config.model.HostSshConfigModel;
import com.orion.ops.module.asset.handler.host.extra.model.HostSshExtraModel;
import com.orion.ops.module.asset.service.HostConfigService;
@@ -79,8 +80,34 @@ public class HostTerminalServiceImpl implements HostTerminalService {
private SystemUserApi systemUserApi;
@Override
public String getHostAccessToken(Long hostId, Long userId) {
log.info("HostConnectService.getHostAccessToken hostId: {}, userId: {}", hostId, userId);
public String getHostTerminalAccessToken(Long userId) {
log.info("HostConnectService.getHostAccessToken userId: {}", userId);
String token = UUIds.random19();
HostTerminalAccessDTO access = HostTerminalAccessDTO.builder()
.token(token)
.userId(userId)
.build();
// 设置缓存
String key = HostTerminalCacheKeyDefine.HOST_TERMINAL_ACCESS.format(token);
RedisStrings.setJson(key, HostTerminalCacheKeyDefine.HOST_TERMINAL_ACCESS, access);
return token;
}
@Override
public HostTerminalAccessDTO getAccessInfoByToken(String token) {
// 获取缓存
String key = HostTerminalCacheKeyDefine.HOST_TERMINAL_ACCESS.format(token);
HostTerminalAccessDTO access = RedisStrings.getJson(key, HostTerminalCacheKeyDefine.HOST_TERMINAL_ACCESS);
// 删除缓存
if (access != null) {
RedisStrings.delete(key);
}
return access;
}
@Override
public HostTerminalConnectDTO getTerminalConnectInfo(Long userId, Long hostId) {
log.info("HostConnectService.getTerminalConnectInfo hostId: {}, userId: {}", hostId, userId);
// 查询主机
HostDO host = hostDAO.selectById(hostId);
Valid.notNull(host, ErrorMessage.HOST_ABSENT);
@@ -115,27 +142,11 @@ public class HostTerminalServiceImpl implements HostTerminalService {
}
}
}
String token = UUIds.random32();
// 获取连接配置
HostSshConnectDTO connect = this.getHostConnectInfo(host, config, extra);
HostTerminalConnectDTO connect = this.getHostConnectInfo(host, config, extra);
connect.setUserId(userId);
connect.setToken(token);
// 设置缓存
String key = HostTerminalCacheKeyDefine.HOST_TERMINAL_CONNECT.format(token);
RedisStrings.setJson(key, HostTerminalCacheKeyDefine.HOST_TERMINAL_CONNECT, connect);
// 记录连接日志
HostConnectLogCreateRequest log = HostConnectLogConvert.MAPPER.to(connect);
log.setUsername(user.getUsername());
hostConnectLogService.create(HostConnectTypeEnum.SSH, log);
// 设置日志参数
OperatorLogs.add(connect);
return token;
}
@Override
public HostSshConnectDTO getConnectInfoByToken(String token) {
String key = HostTerminalCacheKeyDefine.HOST_TERMINAL_CONNECT.format(token);
return RedisStrings.getJson(key, HostTerminalCacheKeyDefine.HOST_TERMINAL_CONNECT);
connect.setToken(UUIds.random15());
return connect;
}
@Override
@@ -148,13 +159,13 @@ public class HostTerminalServiceImpl implements HostTerminalService {
HostSshConfigModel model = hostConfigService.getHostConfig(hostId, HostConfigTypeEnum.SSH);
Valid.notNull(model, ErrorMessage.CONFIG_ABSENT);
// 获取配置
HostSshConnectDTO connect = this.getHostConnectInfo(host, model, null);
HostTerminalConnectDTO connect = this.getHostConnectInfo(host, model, null);
// 打开连接
return this.openSessionStore(connect);
}
@Override
public SessionStore openSessionStore(HostSshConnectDTO conn) {
public SessionStore openSessionStore(HostTerminalConnectDTO conn) {
Long hostId = conn.getHostId();
String address = conn.getHostAddress();
String username = conn.getUsername();
@@ -212,9 +223,9 @@ public class HostTerminalServiceImpl implements HostTerminalService {
* @param extra extra
* @return session
*/
private HostSshConnectDTO getHostConnectInfo(HostDO host,
HostSshConfigModel config,
HostSshExtraModel extra) {
private HostTerminalConnectDTO getHostConnectInfo(HostDO host,
HostSshConfigModel config,
HostSshExtraModel extra) {
// 获取认证方式
HostSshAuthTypeEnum authType = HostSshAuthTypeEnum.of(config.getAuthType());
HostExtraSshAuthTypeEnum extraAuthType = Optional.ofNullable(extra)
@@ -235,7 +246,7 @@ public class HostTerminalServiceImpl implements HostTerminalService {
}
Long keyId = null;
// 填充认证信息
HostSshConnectDTO conn = new HostSshConnectDTO();
HostTerminalConnectDTO conn = new HostTerminalConnectDTO();
conn.setHostId(host.getId());
conn.setHostName(host.getName());
conn.setHostAddress(host.getAddress());