精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Spring Boot如何優雅實現數據加密存儲、模糊匹配和脫敏

冬至子 ? 來源:Shepherd進階筆記 ? 作者:Shepherd ? 2023-06-19 14:42 ? 次閱讀

1.概述

近來我們都在圍繞著使用Spring Boot開發業務系統時如何保證數據安全性這個主題展開總結,當下大部分的B/S架構的系統也都是基于Spring Boot + SpringMVC三層架構開發的,可以認為是在SpringMVC的三層架構中的controller層(邏輯控制層)接口數據進行安全處理操作,更直接點說就是在接口請求參數傳入進行邏輯處理或者響應參數輸出到頁面展示之前就是數據處理的,所以只是在SpringMVC三層架構中的一層中進行安全加固,還不是很穩固,接下來今天我們就再來講講在SpringMVC三層架構另一層中如何進行數據安全加固,在今天主題之前先來看看什么是SpringMVC架構?

什么是SpringMVC三層架構?

SpringMVC的工程結構一般來說分為三層,自下而上是Modle層(模型,數據訪問層)、Cotroller層(控制,邏輯控制層)、View層(視圖,頁面顯示層),其中Modle層分為兩層:dao層、service層,MVC架構分層的主要作用是解耦。采用分層架構的好處,普遍接受的是系統分層有利于系統的維護,系統的擴展。就是增強系統的可維護性和可擴展性。對于Spring這樣的框架,(View\\Web)表示層調用控制層(Controller),控制層調用業務層(Service),業務層調用數據訪問層(Dao) 可以這么說,現在90%以上的業務系統都是基于該三層架構模式開發的,這種架構模式也有人說是設計模式中一種,可見其重要性不言而喻,所以我們需重視。

我們也都知道在日常開發系統過程中,數據安全是非常重要的。特別是在當今互聯網時代,個人隱私安全極其重要,一旦個人用戶數據遭到攻擊泄露,將會造成災難級的事故問題。所有之前我們基于接口層進行數據安全處理是遠遠不夠的,今天我們就來談談如何Model層(數據訪問層)怎樣做到優雅數據加密存儲、模糊匹配及其脫敏展示,本文的主題: 數據加密存儲、模糊匹配和脫敏展示

銀行系統對數據安全性的要求在業務系統中是首屈一指的,所以今天我們就以常見的個人銀行賬戶數據:密碼、手機號、詳細地址、銀行卡號等信息字段為例,進行主題的宣講與淺析。

2.數據加密存儲

我們之前總結的是在接口層進行數據加解密傳輸,也強調過這種方式保證不了數據的絕對安全,只是有效提高接口數據安全性,抬高數據被抓取的門檻而已。所以接下來我們就來講述一下如何在數據的源頭存儲層保障其安全。我們都知道一些核心私密字段,比如說密碼,手機號等在數據庫層存儲就不能明文存儲,必須加密存儲保證即使數據庫泄露了也不會輕易曝光數據。

2.1 優雅實現數據庫字段加解密原理

Mybatis-plus提供企業高級特性就有支持數據加密解密,不過是收費的。。。但是我們可以細細探究其原理進行功能的自我實現。

其實在我們上面推薦的快速開發框架中就已經優雅整合了數據加解密功能了,EncryptTypeHandler:實現數據庫的字段加密與解密。

默認提供了基于base64加密算法Base64EncryptService和AES加密算法AESEncryptService,當然業務側也可以自定義加密算法,這需要實現接口EncryptService,并把實現類注入到容器中即可。加密功能核心邏輯

@Bean
@ConditionalOnMissingBean(EncryptService.class)
public EncryptService encryptService() {
  Algorithm algorithm = encryptProperties.getAlgorithm();
  EncryptService encryptService;
  switch (algorithm) {
    case BASE64:
      encryptService =  new Base64EncryptService();
      break;
    case AES:
      encryptService = new AESEncryptService();
      break;
    default:
      encryptService =  null;
  }
  return encryptService;
}

接下來就可以基于加密算法,擴展mybatis的typeHandler對實體字段數據進行加密解密了:EncryptTypeHandler

public class EncryptTypeHandler< T > extends BaseTypeHandler< T > {

    @Resource
    private EncryptService encryptService;

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, encryptService.encrypt((String)parameter));
    }
    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String columnValue = rs.getString(columnName);
        return StrUtil.isBlank(columnValue) ? (T)columnValue : (T)encryptService.decrypt(columnValue);
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String columnValue = rs.getString(columnIndex);
        return StrUtil.isBlank(columnValue) ? (T)columnValue : (T)encryptService.decrypt(columnValue);
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String columnValue = cs.getString(columnIndex);
        return StrUtil.isBlank(columnValue) ? (T)columnValue : (T)encryptService.decrypt(columnValue);
    }
}

2.2 加密與解密示例

首先創建一張user表:

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL,
  `name` varchar(255) DEFAULT NULL COMMENT '姓名',
  `phone` varchar(255) DEFAULT NULL COMMENT '手機號',
  `id_card` varchar(255) DEFAULT NULL COMMENT '身份證號',
  `bank_card` varchar(255) DEFAULT NULL COMMENT '銀行卡號',
  `address` varchar(255) DEFAULT NULL COMMENT '住址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

這時候我們正常插入一條數據:

@Test
    public void test() {
        User user = new User();
        user.setName("shepherd");
        user.setMobile("17812345678");
        user.setIdCard("213238199601182111");
        user.setBankCard("3222022046741500");
        user.setAddress("杭州市余杭區未來科技城");
        userDAO.insert(user);
    }

數據庫存儲查詢結果如下:

1.jpg

這就是我們平時不加密存儲查詢的結果,這里id是通過分布式id算法自動生成的哈。

接下來我們來看看實現對數據的加密,只需要在配置文件配置使用哪一種加密算法和在實體類的字段屬性加上注解@TableField(typeHandler = EncryptTypeHandler.class)即可。

這里我們使用aes加密算法:

ptc:
  encrypt:
    algorithm: aes

實體類:

@Data
@TableName(autoResultMap = true)
public class User {

    private Long id;
    private String name;

    @TableField(typeHandler = EncryptTypeHandler.class)
    private String mobile;
    @TableField(typeHandler = EncryptTypeHandler.class)
    private String idCard;
    @TableField(typeHandler = EncryptTypeHandler.class)
    private String bankCard;
    @TableField(typeHandler = EncryptTypeHandler.class)
    private String address;
}

再次插入數據,數據庫存儲查詢結果如下:

1.jpg

然后我們可以測試對這條數據進行查詢:

@Test
    public void get() {
        User user = userDAO.selectById(1567405175268642818l);
        System.out.println(user);
    }

結果如下:

User(id=1567405175268642818, name=shepherd, mobile=17812345678, idCard=213238199601182111, bankCard=3222022046741500, address=杭州市余杭區未來科技城)

基于以上完美展示了數據加密存儲和解密查詢。

2.3 數據加密后怎么進行模糊匹配

密碼、手機號、詳細地址、銀行卡號這些信息對加解密的要求也不一樣,比如說密碼我們需要加密存儲,一般使用的都是不可逆的慢hash算法,慢hash算法可以避免暴力破解(典型的用時間換安全性)。

在檢索時我們既不需要解密也不需要模糊查找,直接使用密文完全匹配,但是手機號就不能這樣做,因為手機號我們要查看原信息,并且對手機號還需要支持模糊查找,因此我們今天就針對可逆加解密的數據支持模糊查詢來看看有哪些實現方式。

我們接下來看看常規的做法,也是最廣泛使用的方法,此類方法及滿足的數據安全性,又對查詢友好。

在數據庫實現加密算法函數,在模糊查詢的時候使用decode(key) like '%partial%

在數據庫中實現與程序一致的加解密算法,修改模糊查詢條件,使用數據庫加解密函數先解密再模糊查找,這樣做的優點是實現成本低,開發使用成本低,只需要將以往的模糊查找稍微修改一下就可以實現,但是缺點也很明顯,這樣做無法利用數據庫的索引來優化查詢,甚至有一些數據庫可能無法保證與程序實現一致的加解密算法,但是對于常規的加解密算法都可以保證與應用程序一致。如果對查詢性能要求不是特別高、對數據安全性要求一般,可以使用常見的加解密算法比如說AES、DES之類的也是一個不錯的選擇。

對密文數據進行分詞組合,將分詞組合的結果集分別進行加密,然后存儲到擴展列,查詢時通過key like '%partial%' [先對字符進行固定長度的分組,將一個字段拆分為多個,比如說根據4位英文字符(半角),2個中文字符(全角)為一個檢索條件,舉個例子

shepherd使用4個字符為一組的加密方式,第一組shep ,第二組heph ,第三組ephe ,第四組pher … 依次類推。

如果需要檢索所有包含檢索條件4個字符的數據比如:pher ,加密字符后通過 key like “%partial%” 查庫。

分詞加密實現

public static String splitValueEncrypt(String value, int splitLength) {
        //檢查參數是否合法
        if (StringUtils.isBlank(value) && splitLength <= 0) {
            return null;
        }
        String encryptValue = "";

        //獲取整個字符串可以被切割成字符子串的個數
        int n = (value.length() - splitLength + 1);

        //分詞(規則:分詞長度根據【splitLength】且每次分割的開始跟結束下標加一)
        for (int i = 0; i < n; i++) {
            String splitValue = value.substring(i, splitLength++);
            encryptValue += encrypt(splitValue);
        }

        return encryptValue;
    }

    /**
     * 獲取加密值
     *
     * @param value 加密值
     * @return
     */
    private static String encrypt(String value) {
        // 這里進行加密
        return  null;
    }

基于上面分詞加密保存到擴展列,同時要求對原字段的正刪改查對需要對其相應的擴展列適配,還要注意由于分詞之后導致擴展列的長度可能是原字段幾倍甚至幾十倍,所以務必在開發之前選擇和合適分詞長度和加密算法,一旦加密開始之后,再更改成本就較高了。像如果手機號我們只支持后8位搜索、身份證號只支持后4位搜索,這樣我們就可以通過原字段截取后面位數直接加密存儲到擴展列,不需要再分詞。

3.數據脫敏

實際的業務開發過程中,我們經常需要對用戶的隱私數據進行脫敏處理。所謂脫敏處理其實就是將數據進行混淆隱藏,例如用戶手機信息展示178****5939,以免泄露個人隱私信息。

3.1實現思路

思路比較簡單:在接口返回數據之前按要求對數據進行脫敏加工之后再返回前端。

一開始打算用@ControllerAdvice去實現,但發現需要自己去反射類獲取注解,當返回對象比較復雜,需要遞歸去反射,性能一下子就會降低,于是換種思路,我想到平時使用的@JsonFormat,跟我現在的場景很類似,通過自定義注解跟字段解析器,對字段進行自定義解析。

脫敏字段類型枚舉

public enum MaskEnum {
    /**
     * 中文名
     */
    CHINESE_NAME,
    /**
     * 身份證號
     */
    ID_CARD,
    /**
     * 座機號
     */
    FIXED_PHONE,
    /**
     * 手機號
     */
    MOBILE_PHONE,
    /**
     * 地址
     */
    ADDRESS,
    /**
     * 電子郵件
     */
    EMAIL,
    /**
     * 銀行卡
     */
    BANK_CARD
}

脫敏注解類 :用在脫敏字段之上

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = MaskSerialize.class)
public @interface FieldMask {

    /**
     * 脫敏類型
     * @return
     */
    MaskEnum value();
}

脫敏序列化類

public class MaskSerialize extends JsonSerializer< String > implements ContextualSerializer {

    /**
     * 脫敏類型
     */
    private MaskEnum type;


    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        switch (this.type) {
            case CHINESE_NAME:
            {
                jsonGenerator.writeString(MaskUtils.chineseName(s));
                break;
            }
            case ID_CARD:
            {
                jsonGenerator.writeString(MaskUtils.idCardNum(s));
                break;
            }
            case FIXED_PHONE:
            {
                jsonGenerator.writeString(MaskUtils.fixedPhone(s));
                break;
            }
            case MOBILE_PHONE:
            {
                jsonGenerator.writeString(MaskUtils.mobilePhone(s));
                break;
            }
            case ADDRESS:
            {
                jsonGenerator.writeString(MaskUtils.address(s, 4));
                break;
            }
            case EMAIL:
            {
                jsonGenerator.writeString(MaskUtils.email(s));
                break;
            }
            case BANK_CARD:
            {
                jsonGenerator.writeString(MaskUtils.bankCard(s));
                break;
            }
        }
    }

    @Override
    public JsonSerializer < ? > createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        // 為空直接跳過
        if (beanProperty == null) {
            return serializerProvider.findNullValueSerializer(beanProperty);
        }
        // 非String類直接跳過
        if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
            FieldMask fieldMask = beanProperty.getAnnotation(FieldMask.class);
            if (fieldMask == null) {
                fieldMask = beanProperty.getContextAnnotation(FieldMask.class);
            }
            if (fieldMask != null) {
                // 如果能得到注解,就將注解的 value 傳入 MaskSerialize
                return new MaskSerialize(fieldMask.value());
            }
        }
        return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
    }

    public MaskSerialize() {}

    public MaskSerialize(final MaskEnum type) {
        this.type = type;
    }
}

3.2使用示例

在發送短信記錄的接口上對手機號進行脫敏:

@FieldMask(MaskEnum.MOBILE_PHONE)
    private String mobile;

調用接口返回數據如下:

{
  "code": 200,
  "msg": "OK",
  "data": {
    "list": [
      {
        "id": 1565599123774607362,
        "signId": 8389008488923136,
        "templateId": 8445337328943104,
        "templateType": 1,
        "content": "可愛的${name},博客文章已于${submitTime}上傳更新,請抽空瀏覽。",
        "channelType": 0,
        "mobile": "178****5939",
        "sendStatus": 0,
        "receiveStatus": 0
      }
    ],
    "total": 19,
    "pages": 19
  }
}

4.總結

基于上面內容我們總結如何在數據存儲層進行數據安全加固來達到系統的更安全性,可以這么說沒有最安全的系統只有更安全的系統。所以我們在開發歷程中都會窮極一生去加固系統安全性能。當然了,加強系統安全性的方式還有很多種,我們最近只是圍繞基于Spring BootSpringMVC框架中有效優雅地實現數據安全性,感興趣的小伙伴可以自行了解其他加固方式。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Hash算法
    +關注

    關注

    0

    文章

    43

    瀏覽量

    7379
  • SpringMVC
    +關注

    關注

    0

    文章

    18

    瀏覽量

    5753
收藏 人收藏

    評論

    相關推薦

    Spring Boot如何實現異步任務

    Spring Boot 提供了多種方式來實現異步任務,這里介紹三種主要實現方式。 1、基于注解 @Async @Async 注解是 Spring
    的頭像 發表于 09-30 10:32 ?1407次閱讀

    Spring Boot Starter需要些什么

    pulsar-spring-boot-starter是非常有必要的,在此之前,我們先看看一個starter需要些什么。 Spring Boot Starter spring-boot
    的頭像 發表于 09-25 11:35 ?728次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b> Starter需要些什么

    Spring Boot從零入門1 詳述

    在開始學習Spring Boot之前,我之前從未接觸過Spring相關的項目,Java基礎還是幾年前自學的,現在估計也忘得差不多了吧,寫Spring
    的頭像 發表于 12-10 22:18 ?606次閱讀

    基于Spring Cloud和Euraka的優雅下線以及灰度發布

    該方式借助的是 Spring Boot 應用的 Shutdown hook,應用本身的下線也是優雅的,但如果你的服務發現組件使用的是 Eureka,那么默認最長會有 90 秒的延遲,其他應用才會感知到該服務下線
    的頭像 發表于 04-20 09:52 ?1868次閱讀

    Spring Boot特有的實踐

    Spring Boot是最流行的用于開發微服務的Java框架。在本文中,我將與你分享自2016年以來我在專業開發中使用Spring Boot所采用的最佳實踐。這些內容是基于我的個人經驗
    的頭像 發表于 09-29 10:24 ?878次閱讀

    強大的Spring Boot 3.0要來了

    來源:OSC開源社區(ID:oschina2013) Spring Boot 3.0 首個 RC 已發布,此外還為兩個分支發布了更新:2.7.5 2.6.13。 3.0.0-RC1: https
    的頭像 發表于 10-31 11:17 ?1731次閱讀

    用這4招 優雅實現Spring Boot異步線程間數據傳遞

    Spring Boot 自定義線程池實現異步開發相信看過陳某的文章都了解,但是在實際開發中需要在父子線程之間傳遞一些數據,比如用戶信息,鏈路信息等等
    的頭像 發表于 01-30 10:40 ?1110次閱讀

    Spring Boot Web相關的基礎知識

    Boot的第一個接口。接下來將會將會介紹使用Spring Boot開發Web應用的相關內容,其主要包括使用spring-boot-starter-web組件來
    的頭像 發表于 03-17 15:03 ?624次閱讀

    簡述Spring Boot數據校驗

    上一篇文章我們了解了Spring Boot Web相關的知識,初步了解了spring-boot-starter-web,還了解了@Contrler和@RestController的差別,如果
    的頭像 發表于 03-17 15:07 ?737次閱讀

    如何實現一個注解進行數據脫敏

    、測試 后記 ? 本文主要分享什么是數據脫敏,如何優雅的在項目中運用一個注解實現數據脫敏,為項目
    的頭像 發表于 06-14 09:37 ?975次閱讀
    如何<b class='flag-5'>實現</b>一個注解進行<b class='flag-5'>數據</b><b class='flag-5'>脫敏</b>

    什么是數據脫敏?常用的脫敏規則有哪些呢?

    數據脫敏,指對某些敏感信息通過脫敏規則進行數據的變形,實現敏感隱私數據的可靠保護。
    的頭像 發表于 08-15 10:04 ?2.3w次閱讀
    什么是<b class='flag-5'>數據</b><b class='flag-5'>脫敏</b>?常用的<b class='flag-5'>脫敏</b>規則有哪些呢?

    Spring Boot Actuator快速入門

    不知道大家在寫 Spring Boot 項目的過程中,使用過 Spring Boot Actuator 嗎?知道 Spring
    的頭像 發表于 10-09 17:11 ?606次閱讀

    Spring Boot啟動 Eureka流程

    在上篇中已經說過了 Eureka-Server 本質上是一個 web 應用的項目,今天就來看看 Spring Boot 是怎么啟動 Eureka 的。 Spring Boot 啟動 E
    的頭像 發表于 10-10 11:40 ?854次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b>啟動 Eureka流程

    Spring Boot的啟動原理

    可能很多初學者會比較困惑,Spring Boot 是如何做到將應用代碼和所有的依賴打包成一個獨立的 Jar 包,因為傳統的 Java 項目打包成 Jar 包之后,需要通過 -classpath 屬性
    的頭像 發表于 10-13 11:44 ?623次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b>的啟動原理

    Spring Boot 的設計目標

    什么是Spring Boot Spring BootSpring 開源組織下的一個子項目,也是 S
    的頭像 發表于 10-13 14:56 ?555次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b> 的設計目標