Java对日志文件进行加密

最近碰到了一个新的需求,生产环境中Java程序部署的服务器会定期清理数据,需要将保存在程序所在服务器上的日志文件挂载到网盘上,但又不想让用户看到日志文件中的信息,因此需要对日志文件中的内容进行加密。
这里,并不是对日志文件中的敏感信息进行加密,而是对所有数据都进行加密。上网查了一圈资料之后,最终到了解决方案:自定义Appender,使用AES进行加密。下面贴出具体代码。
AES加密解密工具类

package com.lg.coding.util;

import java.io.*;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AESUtil {
    private static final String ALGORITHM = "AES";
    private static int offset = 16;
    private static final String transformation = "AES/CBC/PKCS5Padding";

    /**
     * AES加密字符串
     * @param password  密钥
     * @param value 待加密字符串
     */
    public static String encrypt(String password, String value) {
        try {
            Key key = generateKey(password);
            //创建初始向量iv用于指定密钥偏移量
            IvParameterSpec iv = new IvParameterSpec(password.getBytes(), 0, offset);
            Cipher cipher = Cipher.getInstance(transformation);
            cipher.init(Cipher.ENCRYPT_MODE, key, iv);
            byte[] encryptedByteValue = cipher.doFinal(value.getBytes("utf-8"));
            String encryptedValue64 = Base64.getEncoder().encodeToString(encryptedByteValue);
            return encryptedValue64;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * AES解密字符串
     * @param password  密钥
     * @param value 待解密字符串
     * @return
     */
    public static String decrypt(String password, String value) {
        try {
            Key key = generateKey(password);
            //创建初始向量iv用于指定密钥偏移量
            IvParameterSpec iv = new IvParameterSpec(password.getBytes(), 0, offset);
            Cipher cipher = Cipher.getInstance(transformation);
            cipher.init(Cipher.DECRYPT_MODE, key, iv);
            byte[] decryptedValue64 = Base64.getDecoder().decode(value);
            byte[] decryptedByteValue = cipher.doFinal(decryptedValue64);
            String decryptedValue = new String(decryptedByteValue,"utf-8");
            return decryptedValue;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * AES解密文件
     * @param password 密钥
     * @param inputFilePath 待解密文件路径
     * @param outputFilePath 输出文件路径
     */
    public static void decryptFile(String password, String inputFilePath, String outputFilePath) {
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            inputStream = new FileInputStream(inputFilePath);
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
            bufferedWriter = new BufferedWriter(new FileWriter(outputFilePath));
            String s;
            while ((s = bufferedReader.readLine()) != null) {
                bufferedWriter.write(decrypt(password, s));
                bufferedWriter.newLine();
                bufferedWriter.flush();
            }
        } catch (FileNotFoundException e) {
            System.out.println("找不到指定文件!");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("文件读取错误!");
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
                bufferedReader.close();
                bufferedWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 生成key
     * @param password
     * @return
     * @throws Exception
     */
    private static Key generateKey(String password) {
        Key key = new SecretKeySpec(password.getBytes(),ALGORITHM);
        return key;
    }
}

在这里,加密操作和解密操作都是针对字符串进行的,在自定义Appender类中,重写subAppend方法,在执行输出文件操作之前对内容进行字符串加密;解密时,逐行读取文件内容后再进行字符串解密。
自定义Appender类

package com.lg.coding.util;

import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.rolling.RollingPolicy;
import ch.qos.logback.core.rolling.RollingPolicyBase;
import ch.qos.logback.core.rolling.RolloverFailure;
import ch.qos.logback.core.rolling.TriggeringPolicy;
import ch.qos.logback.core.rolling.helper.CompressionMode;
import ch.qos.logback.core.rolling.helper.FileNamePattern;
import ch.qos.logback.core.spi.DeferredProcessingAware;
import ch.qos.logback.core.status.ErrorStatus;
import ch.qos.logback.core.util.ContextUtil;
import org.slf4j.event.LoggingEvent;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

public class CustomRollingFileAppender<E> extends FileAppender<E> {
    File currentlyActiveFile;
    TriggeringPolicy<E> triggeringPolicy;
    RollingPolicy rollingPolicy;
    private static String RFA_NO_TP_URL = "http://logback.qos.ch/codes.html#rfa_no_tp";
    private static String RFA_NO_RP_URL = "http://logback.qos.ch/codes.html#rfa_no_rp";
    private static String COLLISION_URL = "http://logback.qos.ch/codes.html#rfa_collision";
    private static String RFA_LATE_FILE_URL = "http://logback.qos.ch/codes.html#rfa_file_after";

    public CustomRollingFileAppender() {
    }

    public void start() {
        if (this.triggeringPolicy == null) {
            this.addWarn("No TriggeringPolicy was set for the RollingFileAppender named " + this.getName());
            this.addWarn("For more information, please visit " + RFA_NO_TP_URL);
        } else if (!this.triggeringPolicy.isStarted()) {
            this.addWarn("TriggeringPolicy has not started. RollingFileAppender will not start");
        } /*else if (this.checkForCollisionsInPreviousRollingFileAppenders()) {
            this.addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");
            this.addError("For more information, please visit " + COLLISION_WITH_EARLIER_APPENDER_URL);
        }*/ else {
            if (!this.append) {
                this.addWarn("Append mode is mandatory for RollingFileAppender. Defaulting to append=true.");
                this.append = true;
            }

            if (this.rollingPolicy == null) {
                this.addError("No RollingPolicy was set for the RollingFileAppender named " + this.getName());
                this.addError("For more information, please visit " + RFA_NO_RP_URL);
            } /*else if (this.checkForFileAndPatternCollisions()) {
                this.addError("File property collides with fileNamePattern. Aborting.");
                this.addError("For more information, please visit " + COLLISION_URL);
            }*/ else {
                if (this.isPrudent()) {
                    if (this.rawFileProperty() != null) {
                        this.addWarn("Setting \"File\" property to null on account of prudent mode");
                        this.setFile((String)null);
                    }

                    if (this.rollingPolicy.getCompressionMode() != CompressionMode.NONE) {
                        this.addError("Compression is not supported in prudent mode. Aborting");
                        return;
                    }
                }

                this.currentlyActiveFile = new File(this.getFile());
                this.addInfo("Active log file name: " + this.getFile());
                super.start();
            }
        }
    }

    /*private boolean checkForFileAndPatternCollisions() {
        if (this.triggeringPolicy instanceof RollingPolicyBase) {
            RollingPolicyBase base = (RollingPolicyBase)this.triggeringPolicy;
            FileNamePattern fileNamePattern = base.fileNamePattern;
            if (fileNamePattern != null && this.fileName != null) {
                String regex = fileNamePattern.toRegex();
                return this.fileName.matches(regex);
            }
        }

        return false;
    }

    private boolean checkForCollisionsInPreviousRollingFileAppenders() {
        boolean collisionResult = false;
        if (this.triggeringPolicy instanceof RollingPolicyBase) {
            RollingPolicyBase base = (RollingPolicyBase)this.triggeringPolicy;
            FileNamePattern fileNamePattern = base.fileNamePattern;
            boolean collisionsDetected = this.innerCheckForFileNamePatternCollisionInPreviousRFA(fileNamePattern);
            if (collisionsDetected) {
                collisionResult = true;
            }
        }

        return collisionResult;
    }*/

    private boolean innerCheckForFileNamePatternCollisionInPreviousRFA(FileNamePattern fileNamePattern) {
        boolean collisionsDetected = false;
        Map<String, FileNamePattern> map = (Map)this.context.getObject("RFA_FILENAME_PATTERN_COLLISION_MAP");
        if (map == null) {
            return collisionsDetected;
        } else {
            Iterator var4 = map.entrySet().iterator();

            while(var4.hasNext()) {
                Map.Entry<String, FileNamePattern> entry = (Map.Entry)var4.next();
                if (fileNamePattern.equals(entry.getValue())) {
                    this.addErrorForCollision("FileNamePattern", ((FileNamePattern)entry.getValue()).toString(), (String)entry.getKey());
                    collisionsDetected = true;
                }
            }

            if (this.name != null) {
                map.put(this.getName(), fileNamePattern);
            }

            return collisionsDetected;
        }
    }

    public void stop() {
        super.stop();
        if (this.rollingPolicy != null) {
            this.rollingPolicy.stop();
        }

        if (this.triggeringPolicy != null) {
            this.triggeringPolicy.stop();
        }

        Map<String, FileNamePattern> map = ContextUtil.getFilenamePatternCollisionMap(this.context);
        if (map != null && this.getName() != null) {
            map.remove(this.getName());
        }

    }

    public void setFile(String file) {
        if (file != null && (this.triggeringPolicy != null || this.rollingPolicy != null)) {
            this.addError("File property must be set before any triggeringPolicy or rollingPolicy properties");
            this.addError("For more information, please visit " + RFA_LATE_FILE_URL);
        }

        super.setFile(file);
    }

    public String getFile() {
        return this.rollingPolicy.getActiveFileName();
    }

    public void rollover() {
        this.lock.lock();

        try {
            this.closeOutputStream();
            this.attemptRollover();
            this.attemptOpenFile();
        } finally {
            this.lock.unlock();
        }

    }

    private void attemptOpenFile() {
        try {
            this.currentlyActiveFile = new File(this.rollingPolicy.getActiveFileName());
            this.openFile(this.rollingPolicy.getActiveFileName());
        } catch (IOException var2) {
            this.addError("setFile(" + this.fileName + ", false) call failed.", var2);
        }

    }

    private void attemptRollover() {
        try {
            this.rollingPolicy.rollover();
        } catch (RolloverFailure var2) {
            this.addWarn("RolloverFailure occurred. Deferring roll-over.");
            this.append = true;
        }

    }

    protected void subAppend(E event) {
        if (this.isStarted()) {
            try {
                if (event instanceof DeferredProcessingAware) {
                    ((DeferredProcessingAware)event).prepareForDeferredProcessing();
                }
                byte[] byteArray = this.encoder.encode(event);
                //加密前数据
                String originalString = new String(byteArray, "UTF-8");
                //加密后数据
                String encryptedString = AESUtil.encrypt("Sanyuan123456789", originalString);
                this.writeBytes((encryptedString + "\n").getBytes());
            } catch (IOException var3) {
                this.started = false;
                this.addStatus(new ErrorStatus("IO failure in appender", this, var3));
            }

        }
    }

    private void writeBytes(byte[] byteArray) throws IOException {
        if (byteArray != null && byteArray.length != 0) {
            this.lock.lock();

            try {
                this.getOutputStream().write(byteArray);
                if (this.isImmediateFlush()) {
                    this.getOutputStream().flush();
                }
            } finally {
                this.lock.unlock();
            }

        }
    }

    public RollingPolicy getRollingPolicy() {
        return this.rollingPolicy;
    }

    public TriggeringPolicy<E> getTriggeringPolicy() {
        return this.triggeringPolicy;
    }

    public void setRollingPolicy(RollingPolicy policy) {
        this.rollingPolicy = policy;
        if (this.rollingPolicy instanceof TriggeringPolicy) {
            this.triggeringPolicy = (TriggeringPolicy)policy;
        }

    }

    public void setTriggeringPolicy(TriggeringPolicy<E> policy) {
        this.triggeringPolicy = policy;
        if (policy instanceof RollingPolicy) {
            this.rollingPolicy = (RollingPolicy)policy;
        }

    }
}

这里是直接复制RollingFileAppender类的代码,对subAppend方法进行重写,在调用writeBytes()方法之前进行加密操作,将加密后的数据输出到本地。
本系统用的日志框架为SpringBoot内置的日志处理框架Logback。将logback-spring.xml文件中系统日志输出对应的Appender标签,class属性改为自定义Appender类的全路径:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <springProperty scope="context" name="logDir" source="logging.path"/>
    <!-- 日志存放路径 -->
    <property name="log.path" value="${logDir}" />
    <!-- 日志输出格式 -->
<!--    <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />-->
    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />

    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>utf8</charset>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${log.pattern}</pattern>
        </layout>
    </appender>

    <!-- 系统日志输出 -->
    <appender name="file_info" class="com.lg.coding.util.CustomRollingFileAppender">
        <file>${log.path}/coding-info.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/coding-info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 日志最大的历史 60-->
            <maxHistory>60</maxHistory>
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>utf8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 只会打印debug不会有info日志-->
            <!--            <level>DEBUG</level>-->
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
            <level>INFO</level>
        </filter>
    </appender>

    <appender name="file_error" class="com.lg.coding.util.CustomRollingFileAppender">
        <file>${log.path}/coding-error.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>coding/.%d{yyyy-MM-dd-HH:mm:ss}.gz</fileNamePattern>
            <fileNamePattern>${log.path}/coding-error.%d{yyyy-MM-dd-HH}.log</fileNamePattern>
            <!-- 日志最大的历史 60-->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>utf8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>ERROR</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="file_debug" class="com.lg.coding.util.CustomRollingFileAppender">
        <file>${log.path}/coding-debug.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/coding-debug.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 日志最大的历史 60-->
            <maxHistory>60</maxHistory>
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>utf8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 只会打印debug不会有info日志-->
            <!--            <level>DEBUG</level>-->
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
            <level>DEBUG</level>
        </filter>
    </appender>

    <!-- 用户访问日志输出  -->
    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/sys-user.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天回滚 daily -->
            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的历史 60-->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!-- 系统模块日志级别控制  -->
    <logger name="com.example" level="debug" />
    <!-- Spring日志级别控制  -->
    <logger name="org.springframework" level="warn" />

    <root level="info">
        <appender-ref ref="console" />
    </root>

    <!--系统操作日志-->
    <root level="info">
        <appender-ref ref="console" />
        <appender-ref ref="file_info" />
        <appender-ref ref="file_error" />
        <appender-ref ref="file_debug" />
    </root>

    <!--系统用户操作日志-->
    <logger name="sys-user" level="info">
        <appender-ref ref="sys-user"/>
    </logger>
</configuration>

此时,启动项目,查看系统本地日志文件:
在这里插入图片描述
可以看到,日志文件的内容已成功加密。
再写一个文件解密接口:

	@ApiOperation(value = "aes文件解密测试")
    @PostMapping("/aesFileDecrypteTest")
    public void aesFileDecrypteTest(String fileInputPath, String fileOutputPath) {
        String key = "Sanyuan123456789";
        AESUtil.decryptFile(key, fileInputPath, fileOutputPath);
    }

其中,fileInputPath和fileOutputPath两个参数分别为待解密文件所在路径和解密后的文件所在路径,接口调用中输入对应参数:
在这里插入图片描述
调用接口,可以看到,指定路径下生成了一个名为“解密日志.log”的文件, 打开文件,查看内容:
在这里插入图片描述
可以看到,内容已被成功解密。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Java 微服务架构设计文档是指一份详细描述Java 微服务架构设计方案和规范的文档。该文档主要用于指导开发人员和架构师在设计和实现Java 微服务架构时的相关工作。在文档中通常包括以下内容: 1. 微服务架构概述:介绍微服务架构的概念、原则和优势,以及适用场景和不适用场景。 2. 技术选型:包括Java 微服务框架、数据库、消息队列、缓存、日志、监控等相关技术的选型和使用原则。 3. 微服务拆分和设计:根据业务模块进行微服务拆分和设计,包括服务边界的划分、服务接口的设计、服务之间的通信机制、数据一致性等。 4. 安全和权限设计:包括微服务间的安全通信、用户认证和授权,以及敏感数据的加密和存储。 5. 高可用和容错设计:包括微服务的部署模式、负载均衡、容错机制、故障转移和恢复机制。 6. 性能和扩展设计:包括服务调用的性能优化、并发控制、扩展性设计和性能监控。 7. 日志和监控设计:包括微服务的日志收集、分析和存储,以及微服务的监控和告警机制。 8. 部署和运维:包括微服务的部署流程、自动化部署、持续集成和持续交付,以及运维和故障排查流程。 总之,Java 微服务架构设计文档是一份包括架构设计、技术选型、安全设计、性能设计、日志监控等方方面面内容的指导性文档,能够帮助开发团队高效、规范地完成Java 微服务架构的设计和实施工作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值