瀏覽代碼

修改core-fss,增加disk模式下的加密存储支持及配置开关

Signed-off-by: luoshi <luoshi@qmth.com.cn>
luoshi 11 月之前
父節點
當前提交
cb839dc944

+ 10 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/config/FileStoreProperty.java

@@ -13,6 +13,8 @@ public class FileStoreProperty {
     @NotNull
     private String server;
 
+    private boolean encrypt = false;
+
     public String getConfig() {
         return config;
     }
@@ -28,4 +30,12 @@ public class FileStoreProperty {
     public void setServer(String server) {
         this.server = server;
     }
+
+    public boolean isEncrypt() {
+        return encrypt;
+    }
+
+    public void setEncrypt(boolean encrypt) {
+        this.encrypt = encrypt;
+    }
 }

+ 27 - 8
core-fss/src/main/java/com/qmth/boot/core/fss/store/impl/DiskStore.java

@@ -3,15 +3,16 @@ package com.qmth.boot.core.fss.store.impl;
 import com.qmth.boot.core.exception.NotFoundException;
 import com.qmth.boot.core.fss.store.FileStore;
 import com.qmth.boot.core.fss.utils.FssSigner;
+import com.qmth.boot.tools.crypto.AES;
 import com.qmth.boot.tools.io.IOUtils;
 import com.qmth.boot.tools.models.ByteArray;
 import com.qmth.boot.tools.uuid.FastUUID;
 import org.springframework.util.Assert;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import java.io.*;
+import java.nio.file.Files;
 import java.time.Duration;
 
 /**
@@ -27,8 +28,11 @@ public class DiskStore implements FileStore {
 
     private FssSigner fssSigner;
 
-    public DiskStore(String rootPath, String server, String temp, FssSigner fssSigner) {
+    private String cipherKey;
+
+    public DiskStore(String rootPath, String server, String temp, FssSigner fssSigner, String cipherKey) {
         this.fssSigner = fssSigner;
+        this.cipherKey = cipherKey;
         this.server = server;
         this.rootDir = new File(rootPath);
         this.tempDir = new File(temp);
@@ -95,8 +99,17 @@ public class DiskStore implements FileStore {
                 throw new RuntimeException("Target file is directory: " + path);
             }
             targetFile.getParentFile().mkdirs();
+            //加密模式下,正式文件写入流需要加密
+            if (cipherKey != null) {
+                OutputStream outputStream = new CipherOutputStream(new FileOutputStream(targetFile),
+                        AES.getEncryptCipher(cipherKey));
+                Files.copy(tempFile.toPath(), outputStream);
+                IOUtils.closeQuietly(outputStream);
+            }
             //临时文件拷贝到正式文件
-            IOUtils.copy(tempFile, targetFile);
+            else {
+                IOUtils.copy(tempFile, targetFile);
+            }
         } finally {
             tempFile.delete();
         }
@@ -107,7 +120,11 @@ public class DiskStore implements FileStore {
         path = formatPath(path);
         File file = new File(rootDir, path);
         if (file.exists() && file.isFile()) {
-            return new FileInputStream(file);
+            InputStream inputStream = new FileInputStream(file);
+            if (cipherKey != null) {
+                inputStream = new CipherInputStream(inputStream, AES.getDecryptCipher(cipherKey));
+            }
+            return inputStream;
         } else {
             throw new NotFoundException("Read file unexist:" + path);
         }
@@ -135,7 +152,9 @@ public class DiskStore implements FileStore {
     public void copy(String source, String target) throws Exception {
         source = formatPath(source);
         target = formatPath(target);
-        IOUtils.copy(new File(rootDir, source), new File(rootDir, target));
+        File targetFile = new File(rootDir, target);
+        targetFile.getParentFile().mkdirs();
+        IOUtils.copy(new File(rootDir, source), targetFile);
     }
 
 }

+ 27 - 2
core-fss/src/main/java/com/qmth/boot/core/fss/utils/FileStoreBuilder.java

@@ -5,25 +5,50 @@ import com.qmth.boot.core.fss.config.FssSecretProvider;
 import com.qmth.boot.core.fss.store.FileStore;
 import com.qmth.boot.core.fss.store.impl.DiskStore;
 import com.qmth.boot.core.fss.store.impl.OssStore;
+import com.qmth.boot.tools.models.ByteArray;
 import org.apache.commons.lang3.StringUtils;
 
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+
 public class FileStoreBuilder {
 
     public static FileStore buildFileStore(String name, FileStoreProperty fileStoreProperty,
             FssSecretProvider fssSecretProvider) {
+        String tempPath = System.getProperty("java.io.tmpdir");
+        //格式化server路径
         String server = StringUtils.trimToEmpty(fileStoreProperty.getServer());
         if (!server.endsWith(FssUtils.FILE_PATH_SEPARATOR)) {
             server = server.concat(FssUtils.FILE_PATH_SEPARATOR);
         }
+        //server匹配时构造签名工具
         FssSigner fssSigner = null;
         if (server.equals(FssUtils.INNER_ENDPOINT_PREFIX.concat(FssUtils.FILE_PATH_SEPARATOR))) {
             fssSigner = new FssSigner(name, fssSecretProvider.getSecret());
         }
         String config = fileStoreProperty.getConfig();
         if (config.startsWith("oss://")) {
-            return new OssStore(new OssConfig(config, server), System.getProperty("java.io.tmpdir"));
+            return new OssStore(new OssConfig(config, server), tempPath);
         } else {
-            return new DiskStore(config, server, System.getProperty("java.io.tmpdir"), fssSigner);
+            //disk模式下启用加密时构造加密工具
+            String cipherKey = null;
+            if (fileStoreProperty.isEncrypt()) {
+                cipherKey = ByteArray.sha1(name + ":" + fssSecretProvider.getSecret()).toHexString().substring(0, 16);
+            }
+            return new DiskStore(config, server, tempPath, fssSigner, cipherKey);
+        }
+    }
+
+    private static Cipher getCipher(String bucket, FssSecretProvider fssSecretProvider) {
+        try {
+            Cipher cipher = Cipher.getInstance("AES");
+            String keyDigest = ByteArray.sha1(bucket + ":" + fssSecretProvider.getSecret()).toHexString();
+            cipher.init(Cipher.ENCRYPT_MODE,
+                    new SecretKeySpec(keyDigest.substring(0, 16).getBytes(StandardCharsets.UTF_8), "AES"));
+            return cipher;
+        } catch (Exception e) {
+            throw new RuntimeException("Build cipher error for bucket: " + bucket, e);
         }
     }
 }

+ 44 - 6
core-fss/src/test/java/com/qmth/boot/test/core/fss/DiskStoreTest.java

@@ -1,24 +1,62 @@
 package com.qmth.boot.test.core.fss;
 
+import com.qmth.boot.core.fss.config.FileStoreProperty;
+import com.qmth.boot.core.fss.config.FssSecretProvider;
 import com.qmth.boot.core.fss.store.impl.DiskStore;
+import com.qmth.boot.core.fss.utils.FileStoreBuilder;
+import com.qmth.boot.core.fss.utils.FssUtils;
 import com.qmth.boot.tools.models.ByteArray;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.junit.Assert;
 import org.junit.Test;
 
-import java.io.ByteArrayInputStream;
-import java.nio.charset.StandardCharsets;
+import java.io.File;
+import java.io.FileInputStream;
+import java.time.Duration;
 
 public class DiskStoreTest {
 
     @Test
     public void test() throws Exception {
-        DiskStore store = new DiskStore("http://server.com", System.getProperty("java.io.tmpdir"),
-                System.getProperty("java.io.tmpdir"), null);
+        FileStoreProperty p = new FileStoreProperty();
+        p.setServer(FssUtils.INNER_ENDPOINT_PREFIX);
+        p.setConfig(System.getProperty("java.io.tmpdir"));
+        FssSecretProvider provider = () -> "qmth";
+        DiskStore store = (DiskStore) FileStoreBuilder.buildFileStore("test", p, provider);
+        condition(store);
+    }
+
+    //@Test
+    public void testEncrypt() throws Exception {
+        FileStoreProperty p = new FileStoreProperty();
+        p.setServer(FssUtils.INNER_ENDPOINT_PREFIX);
+        p.setConfig("/Users/luoshi/test");
+        p.setEncrypt(true);
+        FssSecretProvider provider = () -> "qmth";
+        DiskStore store = (DiskStore) FileStoreBuilder.buildFileStore("test", p, provider);
+        //condition(store);
+        condition(store, new File("/Users/luoshi/Downloads/1.mp4"));
+    }
+
+    private void condition(DiskStore store) throws Exception {
         String content = RandomStringUtils.random(128, true, true);
-        byte[] data = content.getBytes(StandardCharsets.UTF_8);
-        store.write("/1/123.txt", new ByteArrayInputStream(data), ByteArray.md5(data).toBase64());
+        store.write("/1/123.txt", ByteArray.fromString(content).toInputStream(), ByteArray.md5(content).toBase64());
         Assert.assertTrue(store.exist("1/123.txt"));
         Assert.assertEquals(content, ByteArray.fromInputStream(store.read("1/123.txt")).toString());
+        Assert.assertTrue(store.getServerUrl("/1/123.txt", Duration.ofSeconds(30))
+                .contains(FssUtils.INNER_ENDPOINT_PARAM_SIGNATURE));
+        store.copy("/1/123.txt", "2/321.txt");
+        Assert.assertTrue(store.exist("2/321.txt"));
+        Assert.assertEquals(content, ByteArray.fromInputStream(store.read("2/321.txt")).toString());
+        store.delete("/1/123.txt");
+        Assert.assertFalse(store.exist("1/123.txt"));
+    }
+
+    private void condition(DiskStore store, File file) throws Exception {
+        String md5 = ByteArray.md5(file).toBase64();
+        String path = "/1/1.file";
+        store.write(path, new FileInputStream(file), md5);
+        Assert.assertTrue(store.exist(path));
+        Assert.assertEquals(md5, ByteArray.md5(ByteArray.fromInputStream(store.read(path)).value()).toBase64());
     }
 }

+ 26 - 0
tools-common/src/main/java/com/qmth/boot/tools/crypto/AES.java

@@ -23,6 +23,32 @@ public class AES {
 
     private static WeakHashMap<String, IvParameterSpec> vectorMap = new WeakHashMap<>();
 
+    /**
+     * 获取ECB模式加密工具
+     *
+     * @param key
+     * @return
+     * @throws Exception
+     */
+    public static Cipher getEncryptCipher(String key) throws Exception {
+        Cipher cipher = Cipher.getInstance(ECB_ALGORITHM);
+        cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));
+        return cipher;
+    }
+
+    /**
+     * 获取ECB模式解密工具
+     *
+     * @param key
+     * @return
+     * @throws Exception
+     */
+    public static Cipher getDecryptCipher(String key) throws Exception {
+        Cipher cipher = Cipher.getInstance(ECB_ALGORITHM);
+        cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));
+        return cipher;
+    }
+
     /**
      * ECB模式加密
      *