🔨 加密参数.
This commit is contained in:
@@ -27,6 +27,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.visor.framework.web.core.annotation.ParamDecrypt;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
@@ -61,7 +62,7 @@ public class HostIdentityCreateRequest implements Serializable {
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
@Size(max = 512)
|
||||
@ParamDecrypt
|
||||
@Schema(description = "用户密码")
|
||||
private String password;
|
||||
|
||||
|
||||
@@ -61,10 +61,6 @@ public class HostIdentityQueryRequest extends PageRequest {
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
@Size(max = 512)
|
||||
@Schema(description = "用户密码")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "密钥id")
|
||||
private Long keyId;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.visor.common.security.UpdatePasswordAction;
|
||||
import org.dromara.visor.framework.web.core.annotation.ParamDecrypt;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
@@ -66,7 +67,7 @@ public class HostIdentityUpdateRequest implements UpdatePasswordAction {
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
@Size(max = 512)
|
||||
@ParamDecrypt
|
||||
@Schema(description = "用户密码")
|
||||
private String password;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.visor.framework.web.core.annotation.ParamDecrypt;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
@@ -51,14 +52,16 @@ public class HostKeyCreateRequest implements Serializable {
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
|
||||
@ParamDecrypt
|
||||
@Schema(description = "公钥文本")
|
||||
private String publicKey;
|
||||
|
||||
@NotBlank
|
||||
@ParamDecrypt
|
||||
@Schema(description = "私钥文本")
|
||||
private String privateKey;
|
||||
|
||||
@Size(max = 512)
|
||||
@ParamDecrypt
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.visor.common.security.UpdatePasswordAction;
|
||||
import org.dromara.visor.framework.web.core.annotation.ParamDecrypt;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
@@ -56,14 +57,16 @@ public class HostKeyUpdateRequest implements UpdatePasswordAction {
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
|
||||
@ParamDecrypt
|
||||
@Schema(description = "公钥文本")
|
||||
private String publicKey;
|
||||
|
||||
@NotBlank
|
||||
@ParamDecrypt
|
||||
@Schema(description = "私钥文本")
|
||||
private String privateKey;
|
||||
|
||||
@Size(max = 512)
|
||||
@ParamDecrypt
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
|
||||
|
||||
@@ -24,12 +24,12 @@ package org.dromara.visor.module.asset.handler.host.config.strategy;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.Charsets;
|
||||
import cn.orionsec.kit.lang.utils.Exceptions;
|
||||
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.handler.data.strategy.AbstractGenericsDataStrategy;
|
||||
import org.dromara.visor.common.security.PasswordModifier;
|
||||
import org.dromara.visor.common.utils.AesEncryptUtils;
|
||||
import org.dromara.visor.common.utils.RsaEncryptUtils;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.module.asset.dao.HostIdentityDAO;
|
||||
import org.dromara.visor.module.asset.dao.HostKeyDAO;
|
||||
@@ -126,16 +126,18 @@ public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshC
|
||||
after.setPassword(before.getPassword());
|
||||
return;
|
||||
}
|
||||
// 检查是否无密码
|
||||
if (Booleans.isTrue(after.getUseNewPassword()) && Strings.isBlank(after.getPassword())) {
|
||||
throw Exceptions.argument(ErrorMessage.PASSWORD_MISSING);
|
||||
// 使用原始密码
|
||||
if (!Booleans.isTrue(after.getUseNewPassword())) {
|
||||
after.setPassword(before.getPassword());
|
||||
return;
|
||||
}
|
||||
// 检查新密码
|
||||
String newPassword = Valid.notBlank(after.getPassword(), ErrorMessage.PASSWORD_MISSING);
|
||||
// 解密密码
|
||||
newPassword = RsaEncryptUtils.decrypt(newPassword);
|
||||
Valid.notBlank(newPassword, ErrorMessage.DECRYPT_ERROR);
|
||||
// 设置密码
|
||||
String newPassword = PasswordModifier.getEncryptNewPassword(after);
|
||||
if (newPassword == null) {
|
||||
newPassword = before.getPassword();
|
||||
}
|
||||
after.setPassword(newPassword);
|
||||
after.setPassword(AesEncryptUtils.encryptAsString(newPassword));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@ import { dateFormat } from '@/utils';
|
||||
/**
|
||||
* 系统配置类型
|
||||
*/
|
||||
export type SystemSettingType = 'SFTP';
|
||||
export type SystemSettingType = 'SFTP' | 'ENCRYPT';
|
||||
|
||||
/**
|
||||
* 系统设置更新请求
|
||||
@@ -46,6 +46,14 @@ export interface AppReleaseResponse {
|
||||
body: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统聚合设置响应
|
||||
*/
|
||||
export interface SystemSettingAggregateResponse {
|
||||
sftp: SftpSetting;
|
||||
encrypt: EncryptSetting;
|
||||
}
|
||||
|
||||
/**
|
||||
* SFTP 配置
|
||||
*/
|
||||
@@ -53,6 +61,14 @@ export interface SftpSetting {
|
||||
previewSize: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密配置
|
||||
*/
|
||||
export interface EncryptSetting {
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询应用信息
|
||||
*/
|
||||
@@ -60,6 +76,13 @@ export function getSystemAppInfo() {
|
||||
return axios.get<AppInfoResponse>('/infra/system-setting/app-info');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统聚合设置
|
||||
*/
|
||||
export function getSystemAggregateSetting() {
|
||||
return axios.get<SystemSettingAggregateResponse>('/infra/system-setting/setting');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用最新版本信息
|
||||
*/
|
||||
@@ -75,22 +98,29 @@ export function getAppLatestRelease() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统设置
|
||||
* 生成密钥对
|
||||
*/
|
||||
export function generatorKeypair() {
|
||||
return axios.get<EncryptSetting>('/infra/system-setting/generator-keypair');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统设置-单个
|
||||
*/
|
||||
export function updateSystemSetting(request: SystemSettingUpdateRequest) {
|
||||
return axios.put<number>('/infra/system-setting/update', request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新部分系统设置
|
||||
* 更新系统设置-多个
|
||||
*/
|
||||
export function updatePartialSystemSetting(request: SystemSettingUpdateRequest) {
|
||||
return axios.put<number>('/infra/system-setting/update-partial', request);
|
||||
export function updateSystemSettingBatch(request: SystemSettingUpdateRequest) {
|
||||
return axios.put<number>('/infra/system-setting/update-batch', request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询系统设置
|
||||
*/
|
||||
export function getSystemSetting<T>(type: SystemSettingType) {
|
||||
return axios.get<T>('/infra/system-setting/setting', { params: { type } });
|
||||
return axios.get<T>('/infra/system-setting/get', { params: { type } });
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ import type { CacheState, CacheType } from './types';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { TagType } from '@/api/meta/tag';
|
||||
import { getTagList } from '@/api/meta/tag';
|
||||
import type { SystemSettingType } from '@/api/system/setting';
|
||||
import { getSystemSetting } from '@/api/system/setting';
|
||||
import type { HostType } from '@/api/asset/host';
|
||||
import { getHostList } from '@/api/asset/host';
|
||||
import type { PreferenceType } from '@/api/user/preference';
|
||||
@@ -23,6 +21,7 @@ import { getExecJobList } from '@/api/exec/exec-job';
|
||||
import { getPathBookmarkGroupList } from '@/api/asset/path-bookmark-group';
|
||||
import { getCommandSnippetList } from '@/api/asset/command-snippet';
|
||||
import { getPathBookmarkList } from '@/api/asset/path-bookmark';
|
||||
import { getSystemAggregateSetting } from '@/api/system/setting';
|
||||
|
||||
export default defineStore('cache', {
|
||||
state: (): CacheState => ({}),
|
||||
@@ -170,8 +169,8 @@ export default defineStore('cache', {
|
||||
},
|
||||
|
||||
// 加载系统配置
|
||||
async loadSystemSetting<T>(type: SystemSettingType, force = false) {
|
||||
return await this.load(`system_setting_${type}`, () => getSystemSetting<T>(type), undefined, force, {});
|
||||
async loadSystemSetting(force = false) {
|
||||
return await this.load(`system_setting`, getSystemAggregateSetting, undefined, force, {});
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ export type CacheType = 'users' | 'menus' | 'roles'
|
||||
| 'authorizedHostKeys' | 'authorizedHostIdentities'
|
||||
| 'commandSnippetGroups' | 'pathBookmarkGroups'
|
||||
| 'commandSnippets' | 'pathBookmarks'
|
||||
| '*_Tags' | 'preference_*' | 'system_setting_*' | 'footer_setting'
|
||||
| '*_Tags' | 'preference_*' | 'system_setting'
|
||||
| string
|
||||
|
||||
export interface CacheState {
|
||||
|
||||
22
orion-visor-ui/src/utils/rsa.ts
Normal file
22
orion-visor-ui/src/utils/rsa.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { JSEncrypt } from 'jsencrypt';
|
||||
import { useCacheStore } from '@/store';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
// 加密
|
||||
export const encrypt = async (data: string | undefined): Promise<string | undefined> => {
|
||||
// 为空直接返回
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
// 获取公钥
|
||||
const { encrypt } = await useCacheStore().loadSystemSetting();
|
||||
const encryptor = new JSEncrypt();
|
||||
encryptor.setPublicKey(encrypt?.publicKey);
|
||||
// 加密
|
||||
const value = encryptor.encrypt(data);
|
||||
if (value === false) {
|
||||
Message.error('数据加密失败');
|
||||
throw new Error('数据加密失败');
|
||||
}
|
||||
return value;
|
||||
};
|
||||
@@ -33,8 +33,8 @@
|
||||
<template #type="{ record }">
|
||||
<a-tag class="flex-center" :color="getDictValue(hostTypeKey, record.type, 'color')">
|
||||
<!-- 系统类型图标 -->
|
||||
<component v-if="HostOsType[record.osType as keyof typeof HostOsType]"
|
||||
:is="HostOsType[record.osType as keyof typeof HostOsType].icon"
|
||||
<component v-if="getHostOsIcon(record.osType)"
|
||||
:is="getHostOsIcon(record.osType)"
|
||||
class="os-icon" />
|
||||
<!-- 主机类型 -->
|
||||
<span>{{ getDictValue(hostTypeKey, record.type) }}</span>
|
||||
@@ -65,7 +65,7 @@
|
||||
import { flatNodeKeys } from '@/utils/tree';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { hostColumns } from '../types/table.columns';
|
||||
import { HostOsType, hostTypeKey } from '@/views/asset/host-list/types/const';
|
||||
import { getHostOsIcon, hostTypeKey } from '@/views/asset/host-list/types/const';
|
||||
import { getHostGroupRelList } from '@/api/asset/host-group';
|
||||
import HostGroupTree from '@/components/asset/host-group/tree/index.vue';
|
||||
import GrantLayout from './grant-layout.vue';
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
import { useCacheStore, useDictStore } from '@/store';
|
||||
import { dictKeys } from '../types/const';
|
||||
import { getHostConfig, updateHostConfig } from '@/api/asset/host';
|
||||
import { HostType } from '@/views/asset/host-list/types/const';
|
||||
import { encrypt } from '@/utils/rsa';
|
||||
import SshConfigForm from '../components/ssh-config-form.vue';
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
@@ -74,12 +76,22 @@
|
||||
|
||||
// 保存
|
||||
const save = async (conf: Record<string, any>) => {
|
||||
// 加密参数
|
||||
const newConfig = { ...conf };
|
||||
try {
|
||||
// 加密 SSH
|
||||
if (record.value.type === HostType.SSH.value) {
|
||||
newConfig.password = await encrypt(conf.password);
|
||||
}
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setLoading(true);
|
||||
// 更新
|
||||
await updateHostConfig({
|
||||
id: hostConfig.value.id,
|
||||
config: JSON.stringify(conf)
|
||||
config: JSON.stringify(newConfig),
|
||||
});
|
||||
// 设置参数
|
||||
hostConfig.value.config = conf;
|
||||
@@ -147,7 +159,7 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.title{
|
||||
.title {
|
||||
font-weight: 600;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { IdentityType, identityTypeKey } from '../types/const';
|
||||
import { useDictStore } from '@/store';
|
||||
import { encrypt } from '@/utils/rsa';
|
||||
import HostKeySelector from '@/components/asset/host-key/selector/index.vue';
|
||||
|
||||
const { toRadioOptions, getDictValue } = useDictStore();
|
||||
@@ -150,14 +151,21 @@
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
// 加密参数
|
||||
let password = undefined;
|
||||
try {
|
||||
password = await encrypt(formModel.value.password);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
if (isAddHandle.value) {
|
||||
// 新增
|
||||
await createHostIdentity(formModel.value);
|
||||
await createHostIdentity({ ...formModel.value, password });
|
||||
Message.success('创建成功');
|
||||
emits('added');
|
||||
} else {
|
||||
// 修改
|
||||
await updateHostIdentity(formModel.value);
|
||||
await updateHostIdentity({ ...formModel.value, password });
|
||||
Message.success('修改成功');
|
||||
emits('updated');
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
import { createHostKey, updateHostKey, getHostKey } from '@/api/asset/host-key';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { readFileText } from '@/utils/file';
|
||||
import { encrypt } from '@/utils/rsa';
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { loading, setLoading } = useLoading();
|
||||
@@ -200,14 +201,25 @@
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
let publicKey = undefined;
|
||||
let privateKey = undefined;
|
||||
let password = undefined;
|
||||
// 加密参数
|
||||
try {
|
||||
publicKey = await encrypt(formModel.value.publicKey);
|
||||
privateKey = await encrypt(formModel.value.privateKey);
|
||||
password = await encrypt(formModel.value.password);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
if (isAddHandle.value) {
|
||||
// 新增
|
||||
await createHostKey(formModel.value);
|
||||
await createHostKey({ ...formModel.value, publicKey, privateKey, password });
|
||||
Message.success('创建成功');
|
||||
emits('added');
|
||||
} else {
|
||||
// 修改
|
||||
await updateHostKey(formModel.value);
|
||||
await updateHostKey({ ...formModel.value, publicKey, privateKey, password });
|
||||
Message.success('修改成功');
|
||||
emits('updated');
|
||||
}
|
||||
|
||||
@@ -106,8 +106,8 @@
|
||||
<template #title="{ record }">
|
||||
<div class="host-title">
|
||||
<!-- 系统类型图标 -->
|
||||
<component v-if="HostOsType[record.osType as keyof typeof HostOsType]"
|
||||
:is="HostOsType[record.osType as keyof typeof HostOsType].icon"
|
||||
<component v-if="getHostOsIcon(record.osType)"
|
||||
:is="getHostOsIcon(record.osType)"
|
||||
class="os-icon" />
|
||||
<!-- 主机名称 -->
|
||||
<span>{{ record.name }}</span>
|
||||
@@ -232,7 +232,7 @@
|
||||
import { dataColor, objectTruthKeyCount, resetObject } from '@/utils';
|
||||
import { deleteHost, getHostPage, updateHostStatus } from '@/api/asset/host';
|
||||
import { Message, Modal } from '@arco-design/web-vue';
|
||||
import { HostOsType, hostOsTypeKey, hostStatusKey, HostType, hostTypeKey, tagColor } from '../types/const';
|
||||
import { getHostOsIcon, hostOsTypeKey, hostStatusKey, HostType, hostTypeKey, tagColor } from '../types/const';
|
||||
import { copy } from '@/hooks/copy';
|
||||
import { useCacheStore, useDictStore } from '@/store';
|
||||
import { GrantKey, GrantRouteName } from '@/views/asset/grant/types/const';
|
||||
|
||||
@@ -140,8 +140,8 @@
|
||||
<template #type="{ record }">
|
||||
<a-tag class="flex-center" :color="getDictValue(hostTypeKey, record.type, 'color')">
|
||||
<!-- 系统类型图标 -->
|
||||
<component v-if="HostOsType[record.osType as keyof typeof HostOsType]"
|
||||
:is="HostOsType[record.osType as keyof typeof HostOsType].icon"
|
||||
<component v-if="getHostOsIcon(record.osType)"
|
||||
:is="getHostOsIcon(record.osType)"
|
||||
class="os-icon" />
|
||||
<!-- 主机类型 -->
|
||||
<span>{{ getDictValue(hostTypeKey, record.type) }}</span>
|
||||
@@ -267,7 +267,7 @@
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { deleteHost, batchDeleteHost, getHostPage, updateHostStatus } from '@/api/asset/host';
|
||||
import { Message, Modal } from '@arco-design/web-vue';
|
||||
import { tagColor, hostTypeKey, hostStatusKey, HostType, HostOsType, hostOsTypeKey } from '../types/const';
|
||||
import { tagColor, hostTypeKey, hostStatusKey, HostType, hostOsTypeKey, getHostOsIcon } from '../types/const';
|
||||
import { useTablePagination, useRowSelection } from '@/hooks/table';
|
||||
import { useCacheStore, useDictStore } from '@/store';
|
||||
import { copy } from '@/hooks/copy';
|
||||
|
||||
@@ -30,6 +30,11 @@ export const HostOsType = {
|
||||
},
|
||||
};
|
||||
|
||||
// 获取系统类型 icon
|
||||
export const getHostOsIcon = (osType: string) => {
|
||||
return HostOsType[osType as keyof typeof HostOsType]?.icon;
|
||||
};
|
||||
|
||||
// 主机类型 字典项
|
||||
export const hostTypeKey = 'hostType';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user