You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@inlong.apache.org by he...@apache.org on 2022/07/03 06:53:59 UTC
[inlong] 01/03: [INLONG-4774][Manager] Support auth and encryption key user config (#4775)
This is an automated email from the ASF dual-hosted git repository.
healchow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/inlong.git
commit a2145ce4c85142313637eef41559068f975cd5ce
Author: woofyzhao <49...@qq.com>
AuthorDate: Sat Jul 2 17:19:39 2022 +0800
[INLONG-4774][Manager] Support auth and encryption key user config (#4775)
* Support auth and encryption key user config
* Support both encrypted and unencrypted data store
* Update some classes name and property files
Co-authored-by: healchow <he...@gmail.com>
---
.../inlong/manager/common/pojo/user/UserInfo.java | 12 +-
.../inlong/manager/common/util/AESUtils.java | 192 +++++++++++++++++++++
.../inlong/manager/common/util/RSAUtils.java | 122 +++++++++++++
.../inlong/manager/common/util/AESUtilsTest.java | 61 +++++++
.../src/test/resources/application.properties | 24 +++
.../inlong/manager/dao/entity/UserEntity.java | 7 +-
.../main/resources/mappers/UserEntityMapper.xml | 78 +++++++--
.../manager/service/core/impl/UserServiceImpl.java | 48 +++++-
.../main/resources/h2/apache_inlong_manager.sql | 22 ++-
.../manager-web/sql/apache_inlong_manager.sql | 22 ++-
.../src/main/resources/application.properties | 4 +
.../manager/web/controller/AnnoControllerTest.java | 5 +-
.../resources/application.properties | 4 +
13 files changed, 555 insertions(+), 46 deletions(-)
diff --git a/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/pojo/user/UserInfo.java b/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/pojo/user/UserInfo.java
index cc6f9dadc..d4d5ada15 100644
--- a/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/pojo/user/UserInfo.java
+++ b/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/pojo/user/UserInfo.java
@@ -43,8 +43,7 @@ public class UserInfo {
private Integer id;
/**
- * user type
- * {@link UserTypeEnum}
+ * user type {@link UserTypeEnum}
*/
@NotNull
@InEnumInt(UserTypeEnum.class)
@@ -59,6 +58,15 @@ public class UserInfo {
@ApiModelProperty(value = "password", required = true)
private String password;
+ @ApiModelProperty("secret key")
+ private String secretKey;
+
+ @ApiModelProperty("public key")
+ private String publicKey;
+
+ @ApiModelProperty("private key")
+ private String privateKey;
+
@NotNull
@Min(1)
@ApiModelProperty(value = "valid days", required = true)
diff --git a/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/util/AESUtils.java b/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/util/AESUtils.java
new file mode 100644
index 000000000..a3676421f
--- /dev/null
+++ b/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/util/AESUtils.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.inlong.manager.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.SecureRandom;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * AES encryption and decryption utils.
+ */
+@Slf4j
+public class AESUtils {
+
+ private static final int DEFAULT_VERSION = 1;
+ private static final int KEY_SIZE = 128;
+ private static final String ALGORITHM = "AES";
+ private static final String RNG_ALGORITHM = "SHA1PRNG";
+
+ private static final String CONFIG_FILE = "application.properties";
+ private static final String CONFIG_ITEM_ENCRYPT_KEY_PREFIX = "inlong.encrypt.key.value";
+ private static final String CONFIG_ITEM_ENCRYPT_VERSION = "inlong.encrypt.version";
+
+ public static Map<Integer, String> AES_KEY_MAP = new ConcurrentHashMap<>();
+ public static Integer CURRENT_VERSION;
+
+ /**
+ * Load the application properties configuration
+ */
+ private static Properties getApplicationProperties() throws IOException {
+ Properties properties = new Properties();
+ String path = Thread.currentThread().getContextClassLoader().getResource("").getPath() + CONFIG_FILE;
+ InputStream inputStream = new BufferedInputStream(Files.newInputStream(Paths.get(path)));
+ properties.load(inputStream);
+ return properties;
+ }
+
+ /**
+ * Get the current aes key version
+ */
+ public static Integer getCurrentVersion(Properties properties) throws IOException {
+ if (CURRENT_VERSION != null) {
+ return CURRENT_VERSION;
+ }
+
+ if (properties == null) {
+ properties = getApplicationProperties();
+ }
+ String verStr = properties.getProperty(CONFIG_ITEM_ENCRYPT_VERSION);
+ if (StringUtils.isNotEmpty(verStr)) {
+ CURRENT_VERSION = Integer.valueOf(verStr);
+ } else {
+ CURRENT_VERSION = DEFAULT_VERSION;
+ }
+ log.debug("Crypto CURRENT_VERSION = {}", CURRENT_VERSION);
+ return CURRENT_VERSION;
+ }
+
+ /**
+ * Get aes key from config file
+ */
+ public static String getAesKeyByConfig(Integer version) throws Exception {
+ Properties properties = getApplicationProperties();
+ Integer targetVersion = (version == null ? getCurrentVersion(properties) : version);
+ if (StringUtils.isNotEmpty(AES_KEY_MAP.get(targetVersion))) {
+ return AES_KEY_MAP.get(targetVersion);
+ }
+
+ // get aes key under specified version
+ String keyName = CONFIG_ITEM_ENCRYPT_KEY_PREFIX + targetVersion;
+ String aesKey = properties.getProperty(keyName);
+ if (StringUtils.isEmpty(aesKey)) {
+ throw new RuntimeException(String.format("cannot find encryption key %s in application config", keyName));
+ }
+ AES_KEY_MAP.put(targetVersion, aesKey);
+ return aesKey;
+ }
+
+ /**
+ * Generate key
+ */
+ private static SecretKey generateKey(byte[] aesKey) throws Exception {
+ SecureRandom random = SecureRandom.getInstance(RNG_ALGORITHM);
+ random.setSeed(aesKey);
+ KeyGenerator gen = KeyGenerator.getInstance(ALGORITHM);
+ gen.init(KEY_SIZE, random);
+ return gen.generateKey();
+ }
+
+ /**
+ * Encrypt by key
+ */
+ public static byte[] encrypt(byte[] plainBytes, byte[] key) throws Exception {
+ SecretKey secKey = generateKey(key);
+ Cipher cipher = Cipher.getInstance(ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, secKey);
+ return cipher.doFinal(plainBytes);
+ }
+
+ /**
+ * Encrypt by key and current version
+ */
+ public static String encryptToString(byte[] plainBytes, Integer version) throws Exception {
+ if (version == null) {
+ // no encryption
+ return new String(plainBytes, StandardCharsets.UTF_8);
+ }
+ byte[] keyBytes = getAesKeyByConfig(version).getBytes(StandardCharsets.UTF_8);
+ return parseByte2HexStr(encrypt(plainBytes, keyBytes));
+ }
+
+ /**
+ * Decrypt by key and specified version
+ */
+ public static byte[] decrypt(byte[] cipherBytes, byte[] key) throws Exception {
+ SecretKey secKey = generateKey(key);
+ Cipher cipher = Cipher.getInstance(ALGORITHM);
+ cipher.init(Cipher.DECRYPT_MODE, secKey);
+ return cipher.doFinal(cipherBytes);
+ }
+
+ /**
+ * Encrypt by property key
+ */
+ public static byte[] decryptAsString(String cipherText, Integer version) throws Exception {
+ if (version == null) {
+ // No decryption: treated as plain text
+ return cipherText.getBytes(StandardCharsets.UTF_8);
+ }
+ byte[] keyBytes = getAesKeyByConfig(version).getBytes(StandardCharsets.UTF_8);
+ return decrypt(parseHexStr2Byte(cipherText), keyBytes);
+ }
+
+ /**
+ * Parse byte to String in Hex type
+ */
+ public static String parseByte2HexStr(byte[] buf) {
+ StringBuilder strBuf = new StringBuilder();
+ for (byte b : buf) {
+ String hex = Integer.toHexString(b & 0xFF);
+ if (hex.length() == 1) {
+ hex = '0' + hex;
+ }
+ strBuf.append(hex.toUpperCase());
+ }
+ return strBuf.toString();
+ }
+
+ /**
+ * Parse String to byte as Hex type
+ */
+ public static byte[] parseHexStr2Byte(String hexStr) {
+ if (hexStr.length() < 1) {
+ return null;
+ }
+ byte[] result = new byte[hexStr.length() / 2];
+ for (int i = 0; i < hexStr.length() / 2; i++) {
+ int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
+ int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
+ result[i] = (byte) (high * 16 + low);
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/util/RSAUtils.java b/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/util/RSAUtils.java
new file mode 100644
index 000000000..cf3bf9635
--- /dev/null
+++ b/inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/util/RSAUtils.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.inlong.manager.common.util;
+
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import java.io.ByteArrayOutputStream;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * RSA encryption and decryption utils.
+ */
+public class RSAUtils {
+
+ public static final String PUBLIC_KEY = "RSAPublicKey";
+ public static final String PRIVATE_KEY = "RSAPrivateKey";
+ private static final String KEY_ALGORITHM = "RSA";
+ private static final String SIGNATURE_ALGORITHM = "SHA1PRNG";
+ private static final int MAX_ENCRYPT_BLOCK = 117;
+ private static final int MAX_DECRYPT_BLOCK = 128;
+ private static SecureRandom random;
+
+ static {
+ try {
+ random = SecureRandom.getInstance(SIGNATURE_ALGORITHM);
+ } catch (NoSuchAlgorithmException ignored) {
+ // impossible
+ }
+ }
+
+ /**
+ * Generate RSA key pairs
+ */
+ public static Map<String, String> generateRSAKeyPairs() throws NoSuchAlgorithmException {
+ Map<String, String> keyPairMap = new HashMap<>();
+ KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
+ generator.initialize(1024, random);
+ KeyPair keyPair = generator.genKeyPair();
+ RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
+ RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
+ keyPairMap.put(PUBLIC_KEY, Base64.encodeBase64String(publicKey.getEncoded()));
+ keyPairMap.put(PRIVATE_KEY, Base64.encodeBase64String(privateKey.getEncoded()));
+ return keyPairMap;
+ }
+
+ /**
+ * Encryption by public key
+ */
+ public static byte[] encryptByPublicKey(byte[] data, RSAPublicKey publicKey) throws Exception {
+ Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ int inputLen = data.length;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int offSet = 0;
+ byte[] cache;
+ int i = 0;
+ // encrypt data by block
+ while (inputLen - offSet > 0) {
+ if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
+ cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
+ } else {
+ cache = cipher.doFinal(data, offSet, inputLen - offSet);
+ }
+ out.write(cache, 0, cache.length);
+ i++;
+ offSet = i * MAX_ENCRYPT_BLOCK;
+ }
+ byte[] encryptedData = out.toByteArray();
+ out.close();
+ return encryptedData;
+ }
+
+ /**
+ * Decryption by private key
+ */
+ public static byte[] decryptByPrivateKey(byte[] encryptedData, RSAPrivateKey privateKey) throws Exception {
+ Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ int inputLen = encryptedData.length;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int offSet = 0;
+ byte[] cache;
+ int i = 0;
+ // decrypt data by block
+ while (inputLen - offSet > 0) {
+ if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
+ cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
+ } else {
+ cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
+ }
+ out.write(cache, 0, cache.length);
+ i++;
+ offSet = i * MAX_DECRYPT_BLOCK;
+ }
+ byte[] decryptedData = out.toByteArray();
+ out.close();
+ return decryptedData;
+ }
+}
diff --git a/inlong-manager/manager-common/src/test/java/org/apache/inlong/manager/common/util/AESUtilsTest.java b/inlong-manager/manager-common/src/test/java/org/apache/inlong/manager/common/util/AESUtilsTest.java
new file mode 100644
index 000000000..82388272e
--- /dev/null
+++ b/inlong-manager/manager-common/src/test/java/org/apache/inlong/manager/common/util/AESUtilsTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.inlong.manager.common.util;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * AES encryption and decryption util test.
+ */
+public class AESUtilsTest {
+
+ @Test
+ public void testEncryptDecryptDirectly() throws Exception {
+ byte[] key = "key-123".getBytes(StandardCharsets.UTF_8);
+ String plainText = "hello, inlong";
+ byte[] cipheredBytes = AESUtils.encrypt(plainText.getBytes(StandardCharsets.UTF_8), key);
+ byte[] decipheredBytes = AESUtils.decrypt(cipheredBytes, key);
+ Assertions.assertEquals(plainText, new String(decipheredBytes, StandardCharsets.UTF_8));
+ }
+
+ @Test
+ void testEncryptDecryptByConfigVersion() throws Exception {
+ String plainText = "hello, inlong again";
+ Integer version = AESUtils.getCurrentVersion(null);
+ String cipheredText = AESUtils.encryptToString(plainText.getBytes(StandardCharsets.UTF_8), version);
+ byte[] decipheredBytes = AESUtils.decryptAsString(cipheredText, version);
+ Assertions.assertEquals(plainText, new String(decipheredBytes, StandardCharsets.UTF_8));
+ }
+
+ @Test
+ void testEncryptDecryptByNullVersion() throws Exception {
+ String plainText = "hello, inlong again";
+
+ // when version is null no encryption is performed
+ String cipheredText = AESUtils.encryptToString(plainText.getBytes(StandardCharsets.UTF_8), null);
+ Assertions.assertEquals(plainText, cipheredText);
+
+ // when version is null no decryption is performed
+ byte[] decipheredBytes = AESUtils.decryptAsString(cipheredText, null);
+ Assertions.assertEquals(plainText, new String(decipheredBytes, StandardCharsets.UTF_8));
+ }
+}
diff --git a/inlong-manager/manager-common/src/test/resources/application.properties b/inlong-manager/manager-common/src/test/resources/application.properties
new file mode 100644
index 000000000..0481c7a54
--- /dev/null
+++ b/inlong-manager/manager-common/src/test/resources/application.properties
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+
+# Configure auth plugin
+#inlong.auth.type=default
+# Encryption config, the suffix of value must be the same as the version.
+inlong.encrypt.version=1
+inlong.encrypt.key.value1="I!N@L#O$N%G^"
diff --git a/inlong-manager/manager-dao/src/main/java/org/apache/inlong/manager/dao/entity/UserEntity.java b/inlong-manager/manager-dao/src/main/java/org/apache/inlong/manager/dao/entity/UserEntity.java
index 52ccba49d..b4c6d181a 100644
--- a/inlong-manager/manager-dao/src/main/java/org/apache/inlong/manager/dao/entity/UserEntity.java
+++ b/inlong-manager/manager-dao/src/main/java/org/apache/inlong/manager/dao/entity/UserEntity.java
@@ -17,9 +17,10 @@
package org.apache.inlong.manager.dao.entity;
+import lombok.Data;
+
import java.io.Serializable;
import java.util.Date;
-import lombok.Data;
/**
* User entity, including username, password, etc.
@@ -31,6 +32,10 @@ public class UserEntity implements Serializable {
private Integer id;
private String name;
private String password;
+ private String secretKey;
+ private String publicKey;
+ private String privateKey;
+ private Integer encryptVersion;
private Integer accountType;
private Date dueDate;
private Date createTime;
diff --git a/inlong-manager/manager-dao/src/main/resources/mappers/UserEntityMapper.xml b/inlong-manager/manager-dao/src/main/resources/mappers/UserEntityMapper.xml
index 3ecda201f..84c3e3b1d 100644
--- a/inlong-manager/manager-dao/src/main/resources/mappers/UserEntityMapper.xml
+++ b/inlong-manager/manager-dao/src/main/resources/mappers/UserEntityMapper.xml
@@ -24,6 +24,10 @@
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="password" jdbcType="VARCHAR" property="password"/>
+ <result column="secret_key" jdbcType="VARCHAR" property="secretKey"/>
+ <result column="public_key" jdbcType="VARCHAR" property="publicKey"/>
+ <result column="private_key" jdbcType="VARCHAR" property="privateKey"/>
+ <result column="encrypt_version" jdbcType="INTEGER" property="encryptVersion"/>
<result column="account_type" jdbcType="INTEGER" property="accountType"/>
<result column="due_date" jdbcType="TIMESTAMP" property="dueDate"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
@@ -62,8 +66,8 @@
</where>
</sql>
<sql id="Base_Column_List">
- id, name, password, account_type, due_date, create_time, update_time, create_by,
- update_by
+ id, name, password, secret_key, public_key, private_key, encrypt_version,
+ account_type, due_date, create_time, update_time, create_by, update_by
</sql>
<select id="selectByExample" parameterType="org.apache.inlong.manager.dao.entity.UserEntityExample"
resultMap="BaseResultMap">
@@ -93,11 +97,15 @@
</delete>
<insert id="insert" parameterType="org.apache.inlong.manager.dao.entity.UserEntity">
insert into user (id, name, password,
- account_type, due_date, create_time,
- update_time, create_by, update_by)
+ secret_key, public_key, private_key,
+ encrypt_version, account_type, due_date,
+ create_time, update_time,
+ create_by, update_by)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR},
- #{accountType,jdbcType=INTEGER}, #{dueDate,jdbcType=TIMESTAMP}, #{createTime,jdbcType=TIMESTAMP},
- #{updateTime,jdbcType=TIMESTAMP}, #{createBy,jdbcType=VARCHAR}, #{updateBy,jdbcType=VARCHAR})
+ #{secretKey,jdbcType=VARCHAR}, #{publicKey,jdbcType=VARCHAR}, #{privateKey,jdbcType=VARCHAR},
+ #{encryptVersion,jdbcType=INTEGER}, #{accountType,jdbcType=INTEGER}, #{dueDate,jdbcType=TIMESTAMP},
+ #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{createBy,jdbcType=VARCHAR},
+ #{updateBy,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" parameterType="org.apache.inlong.manager.dao.entity.UserEntity">
insert into user
@@ -111,6 +119,18 @@
<if test="password != null">
password,
</if>
+ <if test="secretKey != null">
+ secret_key,
+ </if>
+ <if test="publicKey != null">
+ public_key,
+ </if>
+ <if test="privateKey != null">
+ private_key,
+ </if>
+ <if test="encryptVersion != null">
+ encrypt_version,
+ </if>
<if test="accountType != null">
account_type,
</if>
@@ -140,6 +160,18 @@
<if test="password != null">
#{password,jdbcType=VARCHAR},
</if>
+ <if test="secretKey != null">
+ #{secretKey,jdbcType=VARCHAR},
+ </if>
+ <if test="publicKey != null">
+ #{publicKey,jdbcType=VARCHAR},
+ </if>
+ <if test="privateKey != null">
+ #{privateKey,jdbcType=VARCHAR},
+ </if>
+ <if test="encryptVersion != null">
+ #{encryptVersion,jdbcType=INTEGER},
+ </if>
<if test="accountType != null">
#{accountType,jdbcType=INTEGER},
</if>
@@ -176,6 +208,18 @@
<if test="password != null">
password = #{password,jdbcType=VARCHAR},
</if>
+ <if test="secretKey != null">
+ secret_key = #{secretKey,jdbcType=VARCHAR},
+ </if>
+ <if test="publicKey != null">
+ public_key = #{publicKey,jdbcType=VARCHAR},
+ </if>
+ <if test="privateKey != null">
+ private_key = #{privateKey,jdbcType=VARCHAR},
+ </if>
+ <if test="encryptVersion != null">
+ encrypt_version = #{encryptVersion,jdbcType=INTEGER},
+ </if>
<if test="accountType != null">
account_type = #{accountType,jdbcType=INTEGER},
</if>
@@ -199,14 +243,18 @@
</update>
<update id="updateByPrimaryKey" parameterType="org.apache.inlong.manager.dao.entity.UserEntity">
update user
- set name = #{name,jdbcType=VARCHAR},
- password = #{password,jdbcType=VARCHAR},
- account_type = #{accountType,jdbcType=INTEGER},
- due_date = #{dueDate,jdbcType=TIMESTAMP},
- create_time = #{createTime,jdbcType=TIMESTAMP},
- update_time = #{updateTime,jdbcType=TIMESTAMP},
- create_by = #{createBy,jdbcType=VARCHAR},
- update_by = #{updateBy,jdbcType=VARCHAR}
+ set name = #{name,jdbcType=VARCHAR},
+ password = #{password,jdbcType=VARCHAR},
+ secret_key = #{secretKey,jdbcType=VARCHAR},
+ public_key = #{publicKey,jdbcType=VARCHAR},
+ private_key = #{privateKey,jdbcType=VARCHAR},
+ encrypt_version = #{encryptVersion,jdbcType=INTEGER},
+ account_type = #{accountType,jdbcType=INTEGER},
+ due_date = #{dueDate,jdbcType=TIMESTAMP},
+ create_time = #{createTime,jdbcType=TIMESTAMP},
+ update_time = #{updateTime,jdbcType=TIMESTAMP},
+ create_by = #{createBy,jdbcType=VARCHAR},
+ update_by = #{updateBy,jdbcType=VARCHAR}
where id = #{id,jdbcType=INTEGER}
</update>
-</mapper>
\ No newline at end of file
+</mapper>
diff --git a/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/core/impl/UserServiceImpl.java b/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/core/impl/UserServiceImpl.java
index 727b0617e..46916d48b 100644
--- a/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/core/impl/UserServiceImpl.java
+++ b/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/core/impl/UserServiceImpl.java
@@ -21,14 +21,18 @@ import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomStringUtils;
import org.apache.inlong.manager.common.enums.UserTypeEnum;
+import org.apache.inlong.manager.common.exceptions.BusinessException;
import org.apache.inlong.manager.common.pojo.user.PasswordChangeRequest;
import org.apache.inlong.manager.common.pojo.user.UserDetailListVO;
import org.apache.inlong.manager.common.pojo.user.UserDetailPageRequest;
import org.apache.inlong.manager.common.pojo.user.UserInfo;
+import org.apache.inlong.manager.common.util.AESUtils;
import org.apache.inlong.manager.common.util.CommonBeanUtils;
import org.apache.inlong.manager.common.util.LoginUserUtils;
import org.apache.inlong.manager.common.util.Preconditions;
+import org.apache.inlong.manager.common.util.RSAUtils;
import org.apache.inlong.manager.common.util.SmallTools;
import org.apache.inlong.manager.dao.entity.UserEntity;
import org.apache.inlong.manager.dao.entity.UserEntityExample;
@@ -38,8 +42,10 @@ import org.apache.inlong.manager.service.core.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
+import java.util.Map;
import static org.apache.inlong.manager.common.util.SmallTools.getOverDueDate;
@@ -64,7 +70,6 @@ public class UserServiceImpl implements UserService {
@Override
public UserInfo getById(Integer userId) {
Preconditions.checkNotNull(userId, "User id should not be empty");
-
UserEntity entity = userMapper.selectByPrimaryKey(userId);
Preconditions.checkNotNull(entity, "User not exists with id " + userId);
@@ -74,6 +79,20 @@ public class UserServiceImpl implements UserService {
result.setValidDays(SmallTools.getValidDays(entity.getCreateTime(), entity.getDueDate()));
result.setType(entity.getAccountType());
+ try {
+ // decipher according to stored key version
+ // note that if the version is null then the string is treated as unencrypted plain text
+ Integer version = entity.getEncryptVersion();
+ byte[] secretKeyBytes = AESUtils.decryptAsString(entity.getSecretKey(), version);
+ byte[] publicKeyBytes = AESUtils.decryptAsString(entity.getPublicKey(), version);
+ result.setSecretKey(new String(secretKeyBytes, StandardCharsets.UTF_8));
+ result.setPublicKey(new String(publicKeyBytes, StandardCharsets.UTF_8));
+ } catch (Exception e) {
+ String errMsg = String.format("decryption error: %s", e.getMessage());
+ log.error(errMsg, e);
+ throw new BusinessException(errMsg);
+ }
+
log.debug("success to get user info by id={}", userId);
return result;
}
@@ -82,7 +101,7 @@ public class UserServiceImpl implements UserService {
public boolean create(UserInfo userInfo) {
String username = userInfo.getUsername();
UserEntity userExists = getByName(username);
- Preconditions.checkNull(userExists, "User [" + username + "] already exists");
+ Preconditions.checkNull(userExists, "username [" + username + "] already exists");
UserEntity entity = new UserEntity();
entity.setAccountType(userInfo.getType());
@@ -90,6 +109,22 @@ public class UserServiceImpl implements UserService {
entity.setDueDate(getOverDueDate(userInfo.getValidDays()));
entity.setCreateBy(LoginUserUtils.getLoginUserDetail().getUsername());
entity.setName(username);
+ try {
+ Map<String, String> keyPairs = RSAUtils.generateRSAKeyPairs();
+ String publicKey = keyPairs.get(RSAUtils.PUBLIC_KEY);
+ String privateKey = keyPairs.get(RSAUtils.PRIVATE_KEY);
+ String secretKey = RandomStringUtils.randomAlphanumeric(8);
+ Integer encryptVersion = AESUtils.getCurrentVersion(null);
+ entity.setEncryptVersion(encryptVersion);
+ entity.setPublicKey(AESUtils.encryptToString(publicKey.getBytes(StandardCharsets.UTF_8), encryptVersion));
+ entity.setPrivateKey(AESUtils.encryptToString(privateKey.getBytes(StandardCharsets.UTF_8), encryptVersion));
+ entity.setSecretKey(AESUtils.encryptToString(secretKey.getBytes(StandardCharsets.UTF_8), encryptVersion));
+ } catch (Exception e) {
+ String errMsg = String.format("generate rsa key error: %s", e.getMessage());
+ log.error(errMsg, e);
+ throw new BusinessException(errMsg);
+ }
+
entity.setCreateTime(new Date());
Preconditions.checkTrue(userMapper.insert(entity) > 0, "Create user failed");
@@ -99,13 +134,13 @@ public class UserServiceImpl implements UserService {
@Override
public int update(UserInfo userInfo, String currentUser) {
- Preconditions.checkNotNull(userInfo, "User info should not be null");
- Preconditions.checkNotNull(userInfo.getId(), "User id should not be null");
+ Preconditions.checkNotNull(userInfo, "user info should not be null");
+ Preconditions.checkNotNull(userInfo.getId(), "user id should not be null");
// Whether the current user is an administrator
UserEntity currentUserEntity = getByName(currentUser);
Preconditions.checkTrue(currentUserEntity.getAccountType().equals(UserTypeEnum.ADMIN.getCode()),
- "The current user is not a manager and does not have permission to update users");
+ "current user is not a manager and does not have permission to update users");
UserEntity entity = userMapper.selectByPrimaryKey(userInfo.getId());
Preconditions.checkNotNull(entity, "User not exists with id " + userInfo.getId());
@@ -128,7 +163,6 @@ public class UserServiceImpl implements UserService {
String oldPassword = request.getOldPassword();
String oldPasswordMd = SmallTools.passwordMd5(oldPassword);
Preconditions.checkTrue(oldPasswordMd.equals(entity.getPassword()), "Old password is wrong");
-
String newPasswordMd5 = SmallTools.passwordMd5(request.getNewPassword());
entity.setPassword(newPasswordMd5);
@@ -143,7 +177,7 @@ public class UserServiceImpl implements UserService {
// Whether the current user is an administrator
UserEntity entity = getByName(currentUser);
Preconditions.checkTrue(entity.getAccountType().equals(UserTypeEnum.ADMIN.getCode()),
- "The current user is not a manager and does not have permission to delete users");
+ "current user is not a manager and does not have permission to delete users");
userMapper.deleteByPrimaryKey(userId);
log.debug("success to delete user by id={}, current user={}", userId, currentUser);
diff --git a/inlong-manager/manager-test/src/main/resources/h2/apache_inlong_manager.sql b/inlong-manager/manager-test/src/main/resources/h2/apache_inlong_manager.sql
index 23491c6bc..d91c8be18 100644
--- a/inlong-manager/manager-test/src/main/resources/h2/apache_inlong_manager.sql
+++ b/inlong-manager/manager-test/src/main/resources/h2/apache_inlong_manager.sql
@@ -558,15 +558,19 @@ CREATE TABLE IF NOT EXISTS `stream_sink_field`
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user`
(
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `name` varchar(256) NOT NULL COMMENT 'account name',
- `password` varchar(64) NOT NULL COMMENT 'password md5',
- `account_type` int(11) NOT NULL DEFAULT '1' COMMENT 'account type, 0-manager 1-normal',
- `due_date` datetime DEFAULT NULL COMMENT 'due date for account',
- `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time',
- `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time',
- `create_by` varchar(256) NOT NULL COMMENT 'create by sb.',
- `update_by` varchar(256) DEFAULT NULL COMMENT 'update by sb.',
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Incremental primary key',
+ `name` varchar(256) NOT NULL COMMENT 'Username',
+ `password` varchar(64) NOT NULL COMMENT 'Password md5',
+ `secret_key` varchar(256) DEFAULT NULL COMMENT 'Auth key for public network access',
+ `public_key` text DEFAULT NULL COMMENT 'Public key for asymmetric data encryption',
+ `private_key` text DEFAULT NULL COMMENT 'Private key for asymmetric data encryption',
+ `encrypt_version` int(11) DEFAULT NULL COMMENT 'Encryption key version',
+ `account_type` int(11) NOT NULL DEFAULT '1' COMMENT 'Account type, 0-manager 1-normal',
+ `due_date` datetime DEFAULT NULL COMMENT 'Due date for account',
+ `create_by` varchar(256) NOT NULL COMMENT 'Creator name',
+ `update_by` varchar(256) DEFAULT NULL COMMENT 'Modifier name',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time',
+ `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Modify time',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_user_name` (`name`)
);
diff --git a/inlong-manager/manager-web/sql/apache_inlong_manager.sql b/inlong-manager/manager-web/sql/apache_inlong_manager.sql
index d5a94d94f..295f62081 100644
--- a/inlong-manager/manager-web/sql/apache_inlong_manager.sql
+++ b/inlong-manager/manager-web/sql/apache_inlong_manager.sql
@@ -587,15 +587,19 @@ CREATE TABLE IF NOT EXISTS `stream_sink_field`
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user`
(
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `name` varchar(256) NOT NULL COMMENT 'account name',
- `password` varchar(64) NOT NULL COMMENT 'password md5',
- `account_type` int(11) NOT NULL DEFAULT '1' COMMENT 'account type, 0-manager 1-normal',
- `due_date` datetime DEFAULT NULL COMMENT 'due date for account',
- `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time',
- `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time',
- `create_by` varchar(256) NOT NULL COMMENT 'create by sb.',
- `update_by` varchar(256) DEFAULT NULL COMMENT 'update by sb.',
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Incremental primary key',
+ `name` varchar(256) NOT NULL COMMENT 'Username',
+ `password` varchar(64) NOT NULL COMMENT 'Password md5',
+ `secret_key` varchar(256) DEFAULT NULL COMMENT 'Auth key for public network access',
+ `public_key` text DEFAULT NULL COMMENT 'Public key for asymmetric data encryption',
+ `private_key` text DEFAULT NULL COMMENT 'Private key for asymmetric data encryption',
+ `encrypt_version` int(11) DEFAULT NULL COMMENT 'Encryption key version',
+ `account_type` int(11) NOT NULL DEFAULT '1' COMMENT 'Account type, 0-manager 1-normal',
+ `due_date` datetime DEFAULT NULL COMMENT 'Due date for account',
+ `create_by` varchar(256) NOT NULL COMMENT 'Creator name',
+ `update_by` varchar(256) DEFAULT NULL COMMENT 'Modifier name',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time',
+ `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Modify time',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_user_name` (`name`)
) ENGINE = InnoDB
diff --git a/inlong-manager/manager-web/src/main/resources/application.properties b/inlong-manager/manager-web/src/main/resources/application.properties
index 6d01dc8cf..a05144673 100644
--- a/inlong-manager/manager-web/src/main/resources/application.properties
+++ b/inlong-manager/manager-web/src/main/resources/application.properties
@@ -54,3 +54,7 @@ common.http-client.connectionRequestTimeout=3000
# Configure auth plugin
inlong.auth.type=default
+
+# Encryption config, the suffix of value must be the same as the version.
+inlong.encrypt.version=1
+inlong.encrypt.key.value1="I!N@L#O$N%G^"
diff --git a/inlong-manager/manager-web/src/test/java/org/apache/inlong/manager/web/controller/AnnoControllerTest.java b/inlong-manager/manager-web/src/test/java/org/apache/inlong/manager/web/controller/AnnoControllerTest.java
index 4983fd76b..a779ba72c 100644
--- a/inlong-manager/manager-web/src/test/java/org/apache/inlong/manager/web/controller/AnnoControllerTest.java
+++ b/inlong-manager/manager-web/src/test/java/org/apache/inlong/manager/web/controller/AnnoControllerTest.java
@@ -70,8 +70,7 @@ class AnnoControllerTest extends WebBaseTest {
Response<String> response = getResBody(mvcResult, String.class);
Assertions.assertFalse(response.isSuccess());
- Assertions.assertEquals("Username or password was incorrect, or the account has expired",
- response.getErrMsg());
+ Assertions.assertTrue(response.getErrMsg().contains("incorrect"));
}
@Test
@@ -117,7 +116,7 @@ class AnnoControllerTest extends WebBaseTest {
Response<Boolean> resBody = getResBody(mvcResult, Boolean.class);
Assertions.assertFalse(resBody.isSuccess());
- Assertions.assertEquals("User [admin] already exists", resBody.getErrMsg());
+ Assertions.assertTrue(resBody.getErrMsg().contains("already exists"));
}
@Test
diff --git a/inlong-manager/manager-web/src/main/resources/application.properties b/inlong-manager/manager-web/src/test/resources/application.properties
similarity index 93%
copy from inlong-manager/manager-web/src/main/resources/application.properties
copy to inlong-manager/manager-web/src/test/resources/application.properties
index 6d01dc8cf..a05144673 100644
--- a/inlong-manager/manager-web/src/main/resources/application.properties
+++ b/inlong-manager/manager-web/src/test/resources/application.properties
@@ -54,3 +54,7 @@ common.http-client.connectionRequestTimeout=3000
# Configure auth plugin
inlong.auth.type=default
+
+# Encryption config, the suffix of value must be the same as the version.
+inlong.encrypt.version=1
+inlong.encrypt.key.value1="I!N@L#O$N%G^"