🔨 加密参数.

This commit is contained in:
lijiahang
2025-01-13 15:48:33 +08:00
parent c481cb0ae4
commit f65aa89421
17 changed files with 138 additions and 44 deletions

View File

@@ -27,6 +27,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.visor.framework.web.core.annotation.ParamDecrypt;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
@@ -61,7 +62,7 @@ public class HostIdentityCreateRequest implements Serializable {
@Schema(description = "用户名") @Schema(description = "用户名")
private String username; private String username;
@Size(max = 512) @ParamDecrypt
@Schema(description = "用户密码") @Schema(description = "用户密码")
private String password; private String password;

View File

@@ -61,10 +61,6 @@ public class HostIdentityQueryRequest extends PageRequest {
@Schema(description = "用户名") @Schema(description = "用户名")
private String username; private String username;
@Size(max = 512)
@Schema(description = "用户密码")
private String password;
@Schema(description = "密钥id") @Schema(description = "密钥id")
private Long keyId; private Long keyId;

View File

@@ -28,6 +28,7 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.visor.common.security.UpdatePasswordAction; 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.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@@ -66,7 +67,7 @@ public class HostIdentityUpdateRequest implements UpdatePasswordAction {
@Schema(description = "用户名") @Schema(description = "用户名")
private String username; private String username;
@Size(max = 512) @ParamDecrypt
@Schema(description = "用户密码") @Schema(description = "用户密码")
private String password; private String password;

View File

@@ -27,6 +27,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.visor.framework.web.core.annotation.ParamDecrypt;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
@@ -51,14 +52,16 @@ public class HostKeyCreateRequest implements Serializable {
@Schema(description = "名称") @Schema(description = "名称")
private String name; private String name;
@ParamDecrypt
@Schema(description = "公钥文本") @Schema(description = "公钥文本")
private String publicKey; private String publicKey;
@NotBlank @NotBlank
@ParamDecrypt
@Schema(description = "私钥文本") @Schema(description = "私钥文本")
private String privateKey; private String privateKey;
@Size(max = 512) @ParamDecrypt
@Schema(description = "密码") @Schema(description = "密码")
private String password; private String password;

View File

@@ -28,6 +28,7 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.visor.common.security.UpdatePasswordAction; 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.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@@ -56,14 +57,16 @@ public class HostKeyUpdateRequest implements UpdatePasswordAction {
@Schema(description = "名称") @Schema(description = "名称")
private String name; private String name;
@ParamDecrypt
@Schema(description = "公钥文本") @Schema(description = "公钥文本")
private String publicKey; private String publicKey;
@NotBlank @NotBlank
@ParamDecrypt
@Schema(description = "私钥文本") @Schema(description = "私钥文本")
private String privateKey; private String privateKey;
@Size(max = 512) @ParamDecrypt
@Schema(description = "密码") @Schema(description = "密码")
private String password; private String password;

View File

@@ -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.Booleans;
import cn.orionsec.kit.lang.utils.Charsets; import cn.orionsec.kit.lang.utils.Charsets;
import cn.orionsec.kit.lang.utils.Exceptions;
import cn.orionsec.kit.lang.utils.Strings; import cn.orionsec.kit.lang.utils.Strings;
import org.dromara.visor.common.constant.Const; import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.constant.ErrorMessage; import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.handler.data.strategy.AbstractGenericsDataStrategy; 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.common.utils.Valid;
import org.dromara.visor.module.asset.dao.HostIdentityDAO; import org.dromara.visor.module.asset.dao.HostIdentityDAO;
import org.dromara.visor.module.asset.dao.HostKeyDAO; import org.dromara.visor.module.asset.dao.HostKeyDAO;
@@ -126,16 +126,18 @@ public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshC
after.setPassword(before.getPassword()); after.setPassword(before.getPassword());
return; return;
} }
// 检查是否无密码 // 使用原始密码
if (Booleans.isTrue(after.getUseNewPassword()) && Strings.isBlank(after.getPassword())) { if (!Booleans.isTrue(after.getUseNewPassword())) {
throw Exceptions.argument(ErrorMessage.PASSWORD_MISSING); 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); after.setPassword(AesEncryptUtils.encryptAsString(newPassword));
if (newPassword == null) {
newPassword = before.getPassword();
}
after.setPassword(newPassword);
} }
/** /**

View File

@@ -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; body: string;
} }
/**
* 系统聚合设置响应
*/
export interface SystemSettingAggregateResponse {
sftp: SftpSetting;
encrypt: EncryptSetting;
}
/** /**
* SFTP 配置 * SFTP 配置
*/ */
@@ -53,6 +61,14 @@ export interface SftpSetting {
previewSize: number; 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'); 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) { export function updateSystemSetting(request: SystemSettingUpdateRequest) {
return axios.put<number>('/infra/system-setting/update', request); return axios.put<number>('/infra/system-setting/update', request);
} }
/** /**
* 更新部分系统设置 * 更新系统设置-多个
*/ */
export function updatePartialSystemSetting(request: SystemSettingUpdateRequest) { export function updateSystemSettingBatch(request: SystemSettingUpdateRequest) {
return axios.put<number>('/infra/system-setting/update-partial', request); return axios.put<number>('/infra/system-setting/update-batch', request);
} }
/** /**
* 查询系统设置 * 查询系统设置
*/ */
export function getSystemSetting<T>(type: SystemSettingType) { 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 } });
} }

View File

@@ -2,8 +2,6 @@ import type { CacheState, CacheType } from './types';
import type { AxiosResponse } from 'axios'; import type { AxiosResponse } from 'axios';
import type { TagType } from '@/api/meta/tag'; import type { TagType } from '@/api/meta/tag';
import { getTagList } 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 type { HostType } from '@/api/asset/host';
import { getHostList } from '@/api/asset/host'; import { getHostList } from '@/api/asset/host';
import type { PreferenceType } from '@/api/user/preference'; 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 { getPathBookmarkGroupList } from '@/api/asset/path-bookmark-group';
import { getCommandSnippetList } from '@/api/asset/command-snippet'; import { getCommandSnippetList } from '@/api/asset/command-snippet';
import { getPathBookmarkList } from '@/api/asset/path-bookmark'; import { getPathBookmarkList } from '@/api/asset/path-bookmark';
import { getSystemAggregateSetting } from '@/api/system/setting';
export default defineStore('cache', { export default defineStore('cache', {
state: (): CacheState => ({}), state: (): CacheState => ({}),
@@ -170,8 +169,8 @@ export default defineStore('cache', {
}, },
// 加载系统配置 // 加载系统配置
async loadSystemSetting<T>(type: SystemSettingType, force = false) { async loadSystemSetting(force = false) {
return await this.load(`system_setting_${type}`, () => getSystemSetting<T>(type), undefined, force, {}); return await this.load(`system_setting`, getSystemAggregateSetting, undefined, force, {});
}, },
} }

View File

@@ -6,7 +6,7 @@ export type CacheType = 'users' | 'menus' | 'roles'
| 'authorizedHostKeys' | 'authorizedHostIdentities' | 'authorizedHostKeys' | 'authorizedHostIdentities'
| 'commandSnippetGroups' | 'pathBookmarkGroups' | 'commandSnippetGroups' | 'pathBookmarkGroups'
| 'commandSnippets' | 'pathBookmarks' | 'commandSnippets' | 'pathBookmarks'
| '*_Tags' | 'preference_*' | 'system_setting_*' | 'footer_setting' | '*_Tags' | 'preference_*' | 'system_setting'
| string | string
export interface CacheState { export interface CacheState {

View 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;
};

View File

@@ -33,8 +33,8 @@
<template #type="{ record }"> <template #type="{ record }">
<a-tag class="flex-center" :color="getDictValue(hostTypeKey, record.type, 'color')"> <a-tag class="flex-center" :color="getDictValue(hostTypeKey, record.type, 'color')">
<!-- 系统类型图标 --> <!-- 系统类型图标 -->
<component v-if="HostOsType[record.osType as keyof typeof HostOsType]" <component v-if="getHostOsIcon(record.osType)"
:is="HostOsType[record.osType as keyof typeof HostOsType].icon" :is="getHostOsIcon(record.osType)"
class="os-icon" /> class="os-icon" />
<!-- 主机类型 --> <!-- 主机类型 -->
<span>{{ getDictValue(hostTypeKey, record.type) }}</span> <span>{{ getDictValue(hostTypeKey, record.type) }}</span>
@@ -65,7 +65,7 @@
import { flatNodeKeys } from '@/utils/tree'; import { flatNodeKeys } from '@/utils/tree';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { hostColumns } from '../types/table.columns'; 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 { getHostGroupRelList } from '@/api/asset/host-group';
import HostGroupTree from '@/components/asset/host-group/tree/index.vue'; import HostGroupTree from '@/components/asset/host-group/tree/index.vue';
import GrantLayout from './grant-layout.vue'; import GrantLayout from './grant-layout.vue';

View File

@@ -39,6 +39,8 @@
import { useCacheStore, useDictStore } from '@/store'; import { useCacheStore, useDictStore } from '@/store';
import { dictKeys } from '../types/const'; import { dictKeys } from '../types/const';
import { getHostConfig, updateHostConfig } from '@/api/asset/host'; 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'; import SshConfigForm from '../components/ssh-config-form.vue';
const { visible, setVisible } = useVisible(); const { visible, setVisible } = useVisible();
@@ -74,12 +76,22 @@
// 保存 // 保存
const save = async (conf: Record<string, any>) => { 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 { try {
setLoading(true); setLoading(true);
// 更新 // 更新
await updateHostConfig({ await updateHostConfig({
id: hostConfig.value.id, id: hostConfig.value.id,
config: JSON.stringify(conf) config: JSON.stringify(newConfig),
}); });
// 设置参数 // 设置参数
hostConfig.value.config = conf; hostConfig.value.config = conf;
@@ -147,7 +159,7 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
.title{ .title {
font-weight: 600; font-weight: 600;
user-select: none; user-select: none;
} }

View File

@@ -78,6 +78,7 @@
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { IdentityType, identityTypeKey } from '../types/const'; import { IdentityType, identityTypeKey } from '../types/const';
import { useDictStore } from '@/store'; import { useDictStore } from '@/store';
import { encrypt } from '@/utils/rsa';
import HostKeySelector from '@/components/asset/host-key/selector/index.vue'; import HostKeySelector from '@/components/asset/host-key/selector/index.vue';
const { toRadioOptions, getDictValue } = useDictStore(); const { toRadioOptions, getDictValue } = useDictStore();
@@ -150,14 +151,21 @@
if (error) { if (error) {
return false; return false;
} }
// 加密参数
let password = undefined;
try {
password = await encrypt(formModel.value.password);
} catch (e) {
return false;
}
if (isAddHandle.value) { if (isAddHandle.value) {
// 新增 // 新增
await createHostIdentity(formModel.value); await createHostIdentity({ ...formModel.value, password });
Message.success('创建成功'); Message.success('创建成功');
emits('added'); emits('added');
} else { } else {
// 修改 // 修改
await updateHostIdentity(formModel.value); await updateHostIdentity({ ...formModel.value, password });
Message.success('修改成功'); Message.success('修改成功');
emits('updated'); emits('updated');
} }

View File

@@ -93,6 +93,7 @@
import { createHostKey, updateHostKey, getHostKey } from '@/api/asset/host-key'; import { createHostKey, updateHostKey, getHostKey } from '@/api/asset/host-key';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { readFileText } from '@/utils/file'; import { readFileText } from '@/utils/file';
import { encrypt } from '@/utils/rsa';
const { visible, setVisible } = useVisible(); const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();
@@ -200,14 +201,25 @@
if (error) { if (error) {
return false; 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) { if (isAddHandle.value) {
// 新增 // 新增
await createHostKey(formModel.value); await createHostKey({ ...formModel.value, publicKey, privateKey, password });
Message.success('创建成功'); Message.success('创建成功');
emits('added'); emits('added');
} else { } else {
// 修改 // 修改
await updateHostKey(formModel.value); await updateHostKey({ ...formModel.value, publicKey, privateKey, password });
Message.success('修改成功'); Message.success('修改成功');
emits('updated'); emits('updated');
} }

View File

@@ -106,8 +106,8 @@
<template #title="{ record }"> <template #title="{ record }">
<div class="host-title"> <div class="host-title">
<!-- 系统类型图标 --> <!-- 系统类型图标 -->
<component v-if="HostOsType[record.osType as keyof typeof HostOsType]" <component v-if="getHostOsIcon(record.osType)"
:is="HostOsType[record.osType as keyof typeof HostOsType].icon" :is="getHostOsIcon(record.osType)"
class="os-icon" /> class="os-icon" />
<!-- 主机名称 --> <!-- 主机名称 -->
<span>{{ record.name }}</span> <span>{{ record.name }}</span>
@@ -232,7 +232,7 @@
import { dataColor, objectTruthKeyCount, resetObject } from '@/utils'; import { dataColor, objectTruthKeyCount, resetObject } from '@/utils';
import { deleteHost, getHostPage, updateHostStatus } from '@/api/asset/host'; import { deleteHost, getHostPage, updateHostStatus } from '@/api/asset/host';
import { Message, Modal } from '@arco-design/web-vue'; 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 { copy } from '@/hooks/copy';
import { useCacheStore, useDictStore } from '@/store'; import { useCacheStore, useDictStore } from '@/store';
import { GrantKey, GrantRouteName } from '@/views/asset/grant/types/const'; import { GrantKey, GrantRouteName } from '@/views/asset/grant/types/const';

View File

@@ -140,8 +140,8 @@
<template #type="{ record }"> <template #type="{ record }">
<a-tag class="flex-center" :color="getDictValue(hostTypeKey, record.type, 'color')"> <a-tag class="flex-center" :color="getDictValue(hostTypeKey, record.type, 'color')">
<!-- 系统类型图标 --> <!-- 系统类型图标 -->
<component v-if="HostOsType[record.osType as keyof typeof HostOsType]" <component v-if="getHostOsIcon(record.osType)"
:is="HostOsType[record.osType as keyof typeof HostOsType].icon" :is="getHostOsIcon(record.osType)"
class="os-icon" /> class="os-icon" />
<!-- 主机类型 --> <!-- 主机类型 -->
<span>{{ getDictValue(hostTypeKey, record.type) }}</span> <span>{{ getDictValue(hostTypeKey, record.type) }}</span>
@@ -267,7 +267,7 @@
import { reactive, ref, onMounted } from 'vue'; import { reactive, ref, onMounted } from 'vue';
import { deleteHost, batchDeleteHost, getHostPage, updateHostStatus } from '@/api/asset/host'; import { deleteHost, batchDeleteHost, getHostPage, updateHostStatus } from '@/api/asset/host';
import { Message, Modal } from '@arco-design/web-vue'; 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 { useTablePagination, useRowSelection } from '@/hooks/table';
import { useCacheStore, useDictStore } from '@/store'; import { useCacheStore, useDictStore } from '@/store';
import { copy } from '@/hooks/copy'; import { copy } from '@/hooks/copy';

View File

@@ -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'; export const hostTypeKey = 'hostType';