🔨 加密参数.

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.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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

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.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));
}
/**

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;
}
/**
* 系统聚合设置响应
*/
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 } });
}

View File

@@ -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, {});
},
}

View File

@@ -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 {

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 }">
<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';

View File

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

View File

@@ -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');
}

View File

@@ -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');
}

View File

@@ -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';

View File

@@ -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';

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';