最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

五種SpringBoot實現(xiàn)數(shù)據(jù)加密存儲的方式總結

 更新時間:2023年11月08日 16:16:52   作者:莊周de蝴蝶  
這篇文章主要為大家詳細介紹了五種常見數(shù)據(jù)加密存儲的方法(結合SpringBoot和MyBatisPlus框架進行實現(xiàn)),文中的示例代碼講解詳細,需要的可以參考下

前言

最近由于項目需要做等保,其中有一項要求是系統(tǒng)中的個人信息和業(yè)務信息需要進行加密存儲。經(jīng)過一番搜索,最終總結出了五種數(shù)據(jù)加密存儲的方法(結合SpringBootMyBatisPlus框架進行實現(xiàn)),不知道家人們在項目中使用的是哪種方式,如果有更好地方式也歡迎一起交流~~~,本文所貼出的完整代碼已上傳到GitHub。

思路總覽

在具體講解實現(xiàn)方式之前,先講一下五種方式的思路:

1.手動處理字段加解密

最簡單、樸素的方式。如果項目中只有個別字段,例如密碼字段需要加密,則可以使用這種方法。不過,通常密碼都是做單向 Hash 加密,不存在解密的情況,本文后續(xù)為了統(tǒng)一講解加解密的方式,就對字段統(tǒng)一使用了 AES 對稱加密算法。聽說有的項目需要使用 SM2 之類非對稱加密算法,本文就不再介紹了,只需要參考思路替換相應加解密調用的方法即可。

優(yōu)點:使用簡單、易懂,技術難度低

缺點:全手工處理,容易遺漏,費時。

2.注解結合 AOP 實現(xiàn)

相對簡便的方式,在需要進行加解密處理的字段上添加字段注解,然后在有加解密處理需求的方法上添加方法注解,之后結合 AOP ,對入?yún)⒑头祷亟Y果進行處理即可。

優(yōu)點:使用相對簡單,加解密處理統(tǒng)一在切面處理中完成。

缺點:只能處理入?yún)⒑头祷亟Y果中的字段加解密,如果處理邏輯中涉及到加解密需求還是需要手動處理。同時需要在所有有加解密處理需求的類或方法(可以定義類級別和方法級別的注解,本文只講解方法級別的注解)上添加注解,也容易遺漏,測試時需要特別注意。

3.自定義序列化 / 反序列化類結合注解實現(xiàn)

通過自定義序列化 / 反序列化處理類,然后結合序列化 / 反序列化注解中指定相應類進行實現(xiàn)。

優(yōu)點:使用簡單,加解密處理統(tǒng)一在自定義的序列化化類中完成,只需要在字段上添加注解。

缺點:只能處理序列化數(shù)據(jù)中的加解密,如果業(yè)務邏輯中需要手動設置某個加密字段的值,還是需要手工處理。

4.MybatisPlus自定義TypeHandler實現(xiàn)

和自定義序列化 / 反序列化類的思路類似,不過是和框架功能相耦合,通過使用MybatisPlus自定義TypeHandler實現(xiàn)。

優(yōu)點:使用簡單,加解密處理統(tǒng)一在自定義的TypeHandler中完成,只需要在字段上添加注解。

缺點:只能處理 SQL 的查詢和保存的結果,如果業(yè)務邏輯中需要手動設置某個加密字段的值,還是需要手工處理,如果存在自定義 SQL ,還需要額外添加注解處理,與框架綁定。

5.MybatisPlus自定義攔截器實現(xiàn)

相對底層的方式,結合框架自帶的攔截器功能,通過對 SQL 拼接和處理 SQL 查詢結果進行實現(xiàn)。

優(yōu)點:使用簡單,加解密處理統(tǒng)一在自定義的攔截器中完成,無需使用注解。

缺點:只能處理 SQL 的查詢和保存的結果,如果業(yè)務邏輯中需要手動設置某個加密字段的值,還是需要手工處理,此外還需要整理所有需要加解密操作的字段名,與框架綁定。

小結:當然除了以上幾種方法,根據(jù)使用技術和框架的不同,還有很多種方式,例如 JPA 可以自定義 Convert,類似方法 3 和 4,這里不再介紹。其實,在實現(xiàn)的過程中,有想過通過修改字節(jié)碼,修改字段的 Getter / Setter 方法進行實現(xiàn),但是在實現(xiàn)的時候才發(fā)現(xiàn)兩者的操作是成對的,入庫的時候也就還是明文,不過如果是數(shù)據(jù)脫敏,由于只需要修改 Setter 方法,則可以考慮使用修改字節(jié)碼的方式。

具體實現(xiàn)

手工處理

話不多說,直接上代碼:

public User method1(User user) {
    Long userId = 1L;
    user.setId(userId);
    user.setUsername(AESUtils.encrypt(user.getUsername()));
    user.setPassword(AESUtils.encrypt(user.getPassword()));
    saveOrUpdate(user);
    User resultUser = getById(userId);
    resultUser.setUsername(AESUtils.decrypt(resultUser.getUsername()));
    resultUser.setPassword(AESUtils.decrypt(resultUser.getPassword()));
    return resultUser;
}

這個就不再做詳細解釋了~~~

注解結合 AOP

首先需要定義一個字段注解和方法注解:

/**
 * 字段加密注解
 *
 * @author 莊周de蝴蝶
 * @date 2023-11-07
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
}
/**
 * 方法加密處理注解
 *
 * @author 莊周de蝴蝶
 * @date 2023-11-07
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface EncryptMethod {
?
    /**
     * 需要處理對象在參數(shù)列表中的位置
     */
    int[] value() default { 0 };
?
    /**
     * 是否啟用解密處理
     */
    boolean enableDecrypt() default true;
?
}

然后結合 AOP 去處理方法注解:

/**
 * 處理加密注解切面
 * 特別注意, 這里的排序需要 + 1, 否則會報錯, 具體原因參考鏈接: 
 * <a >...</a>
 *
 * @author 莊周de蝴蝶
 * @date 2023-11-07
 */
@Slf4j
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public class EncryptMethodAspect {
?
    /**
     * 處理加密方法注解
     *
     * @param joinPoint 切點
     * @param encryptMethod 加密方法注解
     * @return 結果
     */
    @Around("@annotation(encryptMethod)")
    public Object around(ProceedingJoinPoint joinPoint, EncryptMethod encryptMethod) throws Throwable {
        try {
            int[] indexArr = encryptMethod.value();
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < indexArr.length; i++) {
                if (i >= args.length) {
                    break;
                }
                // 處理入?yún)⒅械募用?
                handleEncrypt(args[i]);
            }
            Object result = joinPoint.proceed();
            if (encryptMethod.enableDecrypt()) {
                // 對返回結果中的字段進行解密處理
                return handleDecrypt(result);
            }
            return result;
        } catch (Throwable throwable) {
            log.error("加密注解處理出現(xiàn)異常", throwable);
            throw throwable;
        }
    }
?
    /**
     * 對添加了 EncryptField 注解的字段進行加密
     *
     * @param obj 要處理的對象
     */
    private void handleEncrypt(Object obj) throws IllegalAccessException {
        handleEnDecrypt(obj, AESUtils::encrypt);
    }
?
    /**
     * 對添加了 EncryptField 注解的字段進行解密, <b>只考慮了返回值是對象的情況</b>
     *
     * @param obj 要處理的對象
     * @return 結果
     */
    private Object handleDecrypt(Object obj) throws IllegalAccessException {
        return handleEnDecrypt(obj, AESUtils::decrypt);
    }
    
    /**
     * 對添加了 EncryptField 注解的字段進行加解密處理
     *
     * @param obj 要處理的對象
     * @param handleFun 處理函數(shù)
     * @return 結果
     */
    private Object handleEnDecrypt(Object obj, UnaryOperator<String> handleFun) throws IllegalAccessException {
        if (obj == null) {
            return null;
        }
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);
            if (hasSecureField) {
                field.setAccessible(true);
                String val = (String) field.get(obj);
                String result = handleFun.apply(val);
                field.set(obj, result);
            }
        }
        return obj;
    }
?
}

這里只考慮了返回結果是實體對象的情況,如果返回類型的是分頁或者是列表亦或者是類似Result的形式,需要自己進行額外的處理。

最后是使用的方式,首先是在實體的字段上添加注解:

/**
 * 用戶類
 *
 * @author 莊周de蝴蝶
 * @date 2023-10-27
 */
@Data
@TableName("user")
public class User {
    
    /**
     * id
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
?
    /**
     * 用戶名
     */
    @EncryptField
    private String username;
?
    /**
     * 密碼
     */
    @EncryptField
    private String password;
?
}

然后是在方法上添加注解,這里是在控制層使用,當然也可以在ServiceImpl里的方法上使用:

@EncryptMethod
@PostMapping("/method2")
public User method2(@RequestBody User user) {
    return userService.method2(user);
}

自定義序列化注解

首先需要實現(xiàn)序列化 / 反序列化處理類:

/**
 * 解密序列化處理器
 *
 * @author 莊周de蝴蝶
 * @date 2023-11-07
 */
@NoArgsConstructor
public class DecryptSerializer extends JsonSerializer<String> {
?
    @Override
    public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if (StringUtils.isNotBlank(value)) {
            value = AESUtils.decrypt(value);
        }
        jsonGenerator.writeString(value);
    }
?
}
/**
 * 加密序列化處理器
 *
 * @author 莊周de蝴蝶
 * @date 2023-2023-11-07
 */
@NoArgsConstructor
public class EncryptDeserializer extends JsonDeserializer<String> {
?
    @Override
    public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        if (jsonParser != null && StringUtils.isNotBlank(jsonParser.getText())) {
            String text = jsonParser.getText();
            return AESUtils.encrypt(text);
        }
        return null;
    }
?
}

然后定義注解,這里通過使用JacksonJacksonAnnotationsInside注解將序列化和反序列化合并,這樣在使用時就可以只使用一個注解:

/**
 * 字段加解密序列化注解
 *
 * @author 莊周de蝴蝶
 * @date 2023-10-23
 */
@JsonSerialize(using = DecryptSerializer.class)
@JsonDeserialize(using = EncryptDeserializer.class)
@JacksonAnnotationsInside
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptSerializer {
}

最后在相應的實體字段中添加注解即可:

/**
 * 用戶類
 *
 * @author 莊周de蝴蝶
 * @date 2023-10-27
 */
@Data
@TableName("user")
public class User {
    
    /**
     * id
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
?
    /**
     * 用戶名
     */
    @EncryptSerializer
    private String username;
?
    /**
     * 密碼
     */
    @EncryptSerializer
    private String password;
?
}

MybatisPlus自定義TypeHandler

首先是自定義的TypeHandler

/**
 * 加密類型字段處理類
 *
 * @author 莊周de蝴蝶
 * @date 2023-10-27
 */
public class EncryptTypeHandler extends BaseTypeHandler<String> {
?
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, handleResult(parameter, AESUtils::encrypt));
    }
?
    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return handleResult(rs.getString(columnName), AESUtils::decrypt);
    }
?
    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return handleResult(rs.getString(columnIndex), AESUtils::decrypt);
    }
?
    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return handleResult(cs.getString(columnIndex), AESUtils::decrypt);
    }
    
    /**
     * 值加解密處理
     *
     * @param val 值
     * @param fun 處理函數(shù)
     * @return 結果
     */
    private String handleResult(String val, UnaryOperator<String> fun) {
        HttpServletRequest request = ServletUtils.getRequest();
        return StringUtils.isBlank(val) ? val : fun.apply(val);
    }
    
}

然后在相應的實體字段中添加注解即可:

/**
 * 用戶類
 *
 * @author 莊周de蝴蝶
 * @date 2023-10-27
 */
@Data
@TableName("user")
public class User {
    
    /**
     * id
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
?
    /**
     * 用戶名
     */
    @TableField(typeHandler = EncryptTypeHandler.class)
    private String username;
?
    /**
     * 密碼
     */
    @TableField(typeHandler = EncryptTypeHandler.class)
    private String password;
?
}

MybatisPlus自定義攔截器

首先是定義一個保存操作的攔截器,關于攔截器的使用,由于和框架相關聯(lián),這里不再詳細介紹使用方式:

/**
 * 加密更新攔截器處理
 *
 * @author 莊周de蝴蝶
 * @date 2023-10-27
 */
@Configuration
public class EncryptUpdateInterceptor implements InnerInterceptor {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new EncryptUpdateInterceptor());
        return interceptor;
    }
?
    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {
        SQLUtils.handleSql(ms.getConfiguration(), ms.getBoundSql(parameter));
    }
?
}

然后再定義一個查詢操作的攔截器:

/**
 * 加密查詢攔截器處理
 *
 * @author 莊周de蝴蝶
 * @date 2023-11-08
 */
@Component
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
})
public class EncryptQueryInterceptor implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        String sqlId = mappedStatement.getId();
        if (sqlId.contains("selectCount")) {
            return invocation.proceed();
        }
        Object proceed = invocation.proceed();
        @SuppressWarnings("unchecked")
        List<Object> objectList = (List<Object>) proceed;
        if (objectList.isEmpty()) {
            return proceed;
        }
        Class<?> type = objectList.get(0).getClass();
        List<Object> resultList = new ArrayList<>();
        for (Object o : objectList) {
            Map<String, Object> map = JSONUtil.toBean(JSONUtil.toJsonStr(o), new TypeReference<Map<String, Object>>() {}, true);
            for (String keyword : SQLUtils.ENCRYPT_SET) {
                map.put(keyword, AESUtils.decrypt(String.valueOf(map.get(keyword))));
            }
            resultList.add(JSONUtil.toBean(JSONUtil.toJsonStr(map), type));
        }
        return resultList;
    }
?
}

其中SQLUtils的內容如下:

/**
 * sql 工具類
 *
 * @author 莊周de蝴蝶
 * @date 2023-11-08
 */
public class SQLUtils {
    
    public static final Set<String> ENCRYPT_SET = new HashSet<>(Arrays.asList("username", "password"));
    
    private SQLUtils() {}
    
    /**
     * 處理 sql
     *
     * @param configuration 配置
     * @param boundSql sql
     */
    public static void handleSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings.isEmpty() || parameterObject == null) {
           return;
        }
        MetaObject metaObject = configuration.newMetaObject(parameterObject);
        for (ParameterMapping parameterMapping : parameterMappings) {
            String propertyName = parameterMapping.getProperty().toLowerCase();
            Object value = metaObject.getValue(propertyName);
            if (ENCRYPT_SET.contains(propertyName.substring(propertyName.indexOf(".") + 1))) {
                metaObject.setValue(propertyName, AESUtils.encrypt(String.valueOf(value)));
            }
        }
    }
    
}

以上就是五種SpringBoot實現(xiàn)數(shù)據(jù)加密存儲的方式總結的詳細內容,更多關于SpringBoot數(shù)據(jù)加密存儲的資料請關注腳本之家其它相關文章!

相關文章

  • Java中l(wèi)ist.contains()的用法及拓展

    Java中l(wèi)ist.contains()的用法及拓展

    List集合相信大家在開發(fā)過程中幾乎都會用到,有時候難免會遇到集合里的數(shù)據(jù)是重復的,需要進行去除,下面這篇文章主要給大家介紹了關于Java中l(wèi)ist.contains()的用法及拓展的相關資料,需要的朋友可以參考下
    2023-03-03
  • JAVA中try-catch結構之異常處理的使用方法

    JAVA中try-catch結構之異常處理的使用方法

    Java編程中一個非常重要且實用的概念,可以幫助我們處理代碼運行時發(fā)生的異常情況,下面這篇文章主要給大家介紹了關于JAVA中try-catch結構之異常處理的使用方法,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-09-09
  • SpringBoot Admin 如何實現(xiàn)Actuator端點可視化監(jiān)控

    SpringBoot Admin 如何實現(xiàn)Actuator端點可視化監(jiān)控

    這篇文章主要介紹了SpringBoot Admin 如何實現(xiàn)Actuator端點可視化監(jiān)控,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • SpringBoot使用maven實現(xiàn)多環(huán)境運行和打包的操作步驟

    SpringBoot使用maven實現(xiàn)多環(huán)境運行和打包的操作步驟

    在開發(fā)過程中,需要不斷進行環(huán)境的切換和打包部署,maven提供了多環(huán)境配置,可以方便實現(xiàn)不同環(huán)境的配置切換和打包,本文通過代碼示例給大家介紹的非常詳細,需要的朋友可以參考下
    2024-04-04
  • MyBatis版本升級導致OffsetDateTime入?yún)⒔馕霎惓栴}復盤

    MyBatis版本升級導致OffsetDateTime入?yún)⒔馕霎惓栴}復盤

    這篇文章主要介紹了MyBatis版本升級導致OffsetDateTime入?yún)⒔馕霎惓栴}復盤,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-08-08
  • Java-Io-RandomAccessFile任意位置讀寫數(shù)據(jù)的操作小結

    Java-Io-RandomAccessFile任意位置讀寫數(shù)據(jù)的操作小結

    RandomAccessFile類支持隨機訪問方式,可以跳轉到文件的任意位置讀寫數(shù)據(jù),這個類在文件隨機讀取時有很大的優(yōu)勢,可利用多線程完成對一個大文件的讀寫,本文給大家介紹Java-Io-RandomAccessFile(任意位置讀寫數(shù)據(jù))的相關知識,需要的朋友可以參考下
    2022-05-05
  • SpringMVC接收頁面Post提交的json字符串并解析過程

    SpringMVC接收頁面Post提交的json字符串并解析過程

    本文介紹了在SpringMVC中,利用@RequestBody String param接收頁面POST提交的JSON字符串,并通過fastjson進行解析的方法,為開發(fā)者提供了實用參考
    2025-10-10
  • java?Stream流常見操作方法(反射,類加載器,類加載,反射)

    java?Stream流常見操作方法(反射,類加載器,類加載,反射)

    這篇文章主要介紹了java?Stream流常見操作方法(反射,類加載器,類加載,反射),文章圍繞主題展開詳細的內容介紹,展開詳細的內容介紹,具有一定參考價值,感興趣的小伙伴可以參考一下
    2022-06-06
  • Spring中BeanFactory?FactoryBean和ObjectFactory的三種的區(qū)別

    Spring中BeanFactory?FactoryBean和ObjectFactory的三種的區(qū)別

    關于FactoryBean?和?BeanFactory的對比文章比較多,但是對ObjectFactory的描述就比較少,今天我們對比下這三種的區(qū)別,感興趣的朋友跟隨小編一起看看吧
    2023-01-01
  • SpringBoot實現(xiàn)RSA+AES自動接口解密的實戰(zhàn)指南

    SpringBoot實現(xiàn)RSA+AES自動接口解密的實戰(zhàn)指南

    在當今數(shù)據(jù)泄露頻發(fā)的網(wǎng)絡環(huán)境中,接口安全已成為開發(fā)者不可忽視的核心議題,RSA+AES混合加密方案因其安全性高、性能優(yōu)越而被廣泛采用,本文將深入SpringBoot框架,手把手演示如何通過自定義注解+攔截器實現(xiàn)接口的自動化加解密,需要的朋友可以參考下
    2025-08-08

最新評論

天门市| 于田县| 新干县| 饶河县| 雅安市| 绍兴市| 石阡县| 吐鲁番市| 元阳县| 兰考县| 湖口县| 重庆市| 冷水江市| 平武县| 米林县| 龙岩市| 奈曼旗| 自贡市| 泾源县| 沙河市| 沛县| 准格尔旗| 梧州市| 绥江县| 营山县| 星子县| 陈巴尔虎旗| 阿坝| 沙洋县| 彝良县| 锦州市| 望江县| 安乡县| 安顺市| 息烽县| 沙河市| 富蕴县| 韶山市| 车致| 桃园市| 若尔盖县|