SharingSphere的數據脫敏


ShardingSphere 如何抽象數據脫敏?

數據脫敏從概念上講比較容易理解,但在具體實現過程中存在很多方案。在介紹基於數據脫敏的具體開發過程之前,我們有必要先來梳理實現數據脫敏的抽象過程。這里,我將從敏感數據的存儲方式、敏感數據的加解密過程以及在業務代碼中嵌入加解密的過程這三個維度來抽象數據脫敏。

image-20201118202911887

針對每一個維度,我也將基於 ShardingSphere 給出這個框架的具體抽象過程,從而方便你理解使用它的方法和技巧,讓我們來一起看一下。

敏感數據如何存儲?

關於這個問題,要討論的點在於是否需要將敏感數據以明文形式存儲在數據庫中。這個問題的答案並不是絕對的。

我們先來考慮第一種情況。對於一些敏感數據而言,我們顯然應該直接以密文的形式將加密之后的數據進行存儲,防止有任何一種途徑能夠從數據庫中獲取這些數據明文。 在這類敏感數據中,最典型的就是用戶密碼,我們通常會采用 MD5 等不可逆的加密算法對其進行加密,而使用這些數據的方法也只是依賴於它的密文形式,不會涉及對明文的直接處理。

但對於用戶姓名、手機號等信息,由於統計分析等方面的需要,顯然我們不能直接采用不可逆的加密算法對其進行加密,還需要將明文信息進行處理。一種常見的處理方式是將一個字段用兩列來進行保存,一列保存明文,一列保存密文,這就是第二種情況。

顯然,我們可以將第一種情況看作是第二種情況的特例。也就是說,在第一種情況中沒有明文列,只有密文列。

ShardingSphere 同樣基於這兩種情況進行了抽象,它將這里的明文列命名為 plainColumn,而將密文列命名為 cipherColumn。其中 plainColumn 屬於選填,而 cipherColumn 則是必填。同時,ShardingSphere 還提出了一個邏輯列 logicColumn 的概念,該列代表一種虛擬列,只面向開發人員進行編程使用:

image-20201118202937226

敏感數據如何加解密?

數據脫敏本質上就是一種加解密技術應用場景,自然少不了對各種加解密算法和技術的封裝。傳統的加解密方式有兩種,一種是對稱加密,常見的包括 DEA 和 AES;另一種是非對稱加密,常見的包括 RSA。

ShardingSphere 內部也抽象了一個 ShardingEncryptor 組件專門封裝各種加解密操作:

image-20201118203045023

目前,ShardingSphere 內置了 AESShardingEncryptor 和 MD5ShardingEncryptor 這兩個具體的 ShardingEncryptor 實現。當然,由於 ShardingEncryptor 擴展了 TypeBasedSPI 接口,所以開發人員完全可以基於微內核架構和 JDK 所提供的 SPI 機制來實現和動態加載自定義的各種 ShardingEncryptor。我們會在“微內核架構:ShardingSphere 如何實現系統的擴展性?”這個課時中對 ShardingSphere 中的微內核架構和 SPI 機制進行詳細的討論。

業務代碼中如何嵌入數據脫敏?

數據脫敏的最后一個抽象點在於如何在業務代碼中嵌入數據脫敏過程,顯然這個過程應該盡量做到自動化,並且具備低侵入性,且應該對開發人員足夠透明。

我們可以通過一個具體的示例來描述數據脫敏的執行流程。假設系統中存在一張 user 表,其中包含一個 user_name 列。我們認為這個 user_name 列屬於敏感數據,需要對其進行數據脫敏。那么按照前面討論的數據存儲方案,可以在 user 表中設置兩個字段,一個代表明文的 user_name_plain,一個代表密文的 user_name_cipher。然后應用程序通過 user_name 這個邏輯列與數據庫表進行交互:

image-20201118203126611

針對這個交互過程,我們希望存在一種機制,能夠自動將 user_name 邏輯列映射到 user_name_plain 和 user_name_cipher 列。同時,我們希望提供一種配置機制,能夠讓開發人員根據需要靈活指定脫敏過程中所采用的各種加解密算法。

作為一款優秀的開源框架,ShardingSphere 就提供了這樣一種機制。那么它是如何做到這一點呢?

首先,ShardingSphere 通過對從應用程序傳入的 SQL 進行解析,並依據開發人員提供的脫敏配置對 SQL 進行改寫,從而實現對明文數據的自動加密,並將加密后的密文數據存儲到數據庫中。當我們查詢數據時,它又從數據庫中取出密文數據,並自動對其解密,最終將解密后的明文數據返回給用戶。ShardingSphere 提供了自動化+透明化的數據脫敏過程,業務開發人員可以像使用普通數據那樣使用脫敏數據,而不需要關注數據脫敏的實現細節。

系統改造:如何實現數據脫敏?

接下來,就讓我們繼續對系統進行改造,並添加數據脫敏功能吧。這個過程主要有三個步驟:准備數據脫敏、配置數據脫敏和執行數據脫敏。

准備數據脫敏

為了演示數據脫敏功能,我們重新定義一個 EncryptUser 實體類,該類中定義了與數據脫敏相關的常見用戶名、密碼等字段,這些字段與數據庫中 encrypt_user 表的列是一一對應的:

public class EncryptUser {
	
	private Long userId;
    
    private String userName;
    
    private String userNamePlain;
    
    private String pwd;
    
    private String assistedQueryPwd;
    
    public Long getUserId() {
        return userId;
    }
    ...
}

接下來,我們有必要提一下 EncryptUserMapper 中關於 resultMap 和 insert 語句的定義,如下所示:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tianyilan.shardingsphere.demo.repository.EncryptUserRepository">
    <resultMap id="encryptUserMap" type="com.tianyilan.shardingsphere.demo.entity.EncryptUser">
        <result column="user_id" property="userId" jdbcType="INTEGER"/>
        <result column="user_name" property="userName" jdbcType="VARCHAR"/>
        <result column="pwd" property="pwd" jdbcType="VARCHAR"/>
        <result column="assisted_query_pwd" property="assistedQueryPwd" jdbcType="VARCHAR"/>
    </resultMap>

    <insert id="addEntity">
        INSERT INTO encrypt_user (user_id, user_name, pwd) VALUES (#{userId,jdbcType=INTEGER}, #{userName,jdbcType=VARCHAR}, #{pwd,jdbcType=VARCHAR})
    </insert>

    <delete id="deleteEntity">
        DELETE FROM encrypt_user WHERE user_id = #{userId,jdbcType=INTEGER};
    </delete>

    <select id="findEntities" resultMap="encryptUserMap">
        SELECT * FROM encrypt_user;
    </select>
</mapper>

請注意,我們在 resultMap 中並沒有指定 user_name_plain 字段,同時,insert 語句中同樣沒有指定這個字段。

有了 Mapper,我們就可以構建 Service 層組件。在這個 EncryptUserServiceImpl 類中,我們分別提供了 processEncryptUsers 和 getEncryptUsers 方法來插入用戶以及獲取用戶列表。

@Service
public class EncryptUserServiceImpl implements EncryptUserService {

	@Autowired
	private EncryptUserRepository encryptUserRepository;
	
	@Override
	public void processEncryptUsers() throws SQLException {
		insertEncryptUsers();
	}
	
	private List<Long> insertEncryptUsers() throws SQLException {
		List<Long> result = new ArrayList<>(10);
        for (Long i = 1L; i <= 10; i++) {
        	EncryptUser encryptUser = new EncryptUser();
        	encryptUser.setUserId(i);
        	encryptUser.setUserName("test_" + i);
        	encryptUser.setPwd("pwd" + i);
            encryptUserRepository.addEntity(encryptUser);
            result.add(encryptUser.getUserId());
        }
        
        return result;		
	}

	@Override
	public List<EncryptUser> getEncryptUsers() throws SQLException {
		return encryptUserRepository.findEntities();
	}
}

現在,業務層代碼已經准備就緒。由於數據脫敏功能內嵌在 sharding-jdbc-spring-boot-starter 中,所以我們不需要引入額外的依賴包。

配置數據脫敏

在整體架構上,和分庫分表以及讀寫分離一樣,數據脫敏對外暴露的入口也是一個符合 JDBC 規范的 EncryptDataSource 對象。如下面的代碼所示,ShardingSphere 提供了 EncryptDataSourceFactory 工廠類,完成了 EncryptDataSource 對象的構建:

public final class EncryptDataSourceFactory {
    public static DataSource createDataSource(DataSource dataSource, EncryptRuleConfiguration encryptRuleConfiguration, Properties props) throws SQLException {
        return new EncryptDataSource(dataSource, new EncryptRule(encryptRuleConfiguration), props);
    }

    private EncryptDataSourceFactory() {
    }
}

可以看到,這里存在一個 EncryptRuleConfiguration 類,該類中包含了兩個 Map,分別用來配置加解密器列表以及加密表配置列表:

image-20201118203611985

其中 EncryptorRuleConfiguration 集成了 ShardingSphere 中的一個通用抽象類 TypeBasedSPIConfiguration,包含了 type 和 properties 這兩個字段:

image-20201118203848232

而 EncryptTableRuleConfiguration 內部是一個包含多個 EncryptColumnRuleConfiguration 的 Map,這個 EncryptColumnRuleConfiguration 就是 ShardingSphere 中對加密列的配置,包含了 plainColumn、cipherColumn 的定義:

image-20201118204016056

作為總結,我們通過一張圖羅列出各個配置類之間的關系,以及數據脫敏所需要配置的各項內容:

image-20201118204100770

現在回到代碼,為了實現數據脫敏,我們首先需要定義一個數據源,這里命名為 dsencrypt:

spring.shardingsphere.datasource.names=dsencrypt
spring.shardingsphere.datasource.dsencrypt.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.dsencrypt.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.dsencrypt.jdbc-url=jdbc:mysql://localhost:3306/dsencrypt
spring.shardingsphere.datasource.dsencrypt.username=root
spring.shardingsphere.datasource.dsencrypt.password=root

配置成功之后,我們再配置加密器,這里定義 name_encryptor 和 pwd_encryptor 這兩個加密器,分別用於對 user_name 列和 pwd 列進行加解密。注意,在下面這段代碼中,對於 name_encryptor,我們使用了對稱加密算法 AES;而對於 pwd_encryptor,我們則直接使用不可逆的 MD5 散列算法:

spring.shardingsphere.encrypt.encryptors.name_encryptor.type=aes
spring.shardingsphere.encrypt.encryptors.name_encryptor.props.aes.key.value=123456
spring.shardingsphere.encrypt.encryptors.pwd_encryptor.type=md5

接下來,我們需要完成脫敏表的配置。針對案例中的場景,我們可以選擇對 user_name 列設置 plainColumn、cipherColumn 以及 encryptor 屬性,而對於 pwd 列而言,由於我們不希望在數據庫中存儲明文,所以只需要配置 cipherColumn 和 encryptor 屬性就可以了。

spring.shardingsphere.encrypt.tables.encrypt_user.columns.user_name.plainColumn=user_name_plain
spring.shardingsphere.encrypt.tables.encrypt_user.columns.user_name.cipherColumn=user_name
spring.shardingsphere.encrypt.tables.encrypt_user.columns.user_name.encryptor=name_encryptor
spring.shardingsphere.encrypt.tables.encrypt_user.columns.pwd.cipherColumn=pwd
spring.shardingsphere.encrypt.tables.encrypt_user.columns.pwd.encryptor=pwd_encryptor

最后,ShardingSphere 還提供了一個屬性開關,當底層數據庫表里同時存儲了明文和密文數據后,該屬性開關可以決定是直接查詢數據庫表里的明文數據進行返回,還是查詢密文數據並進行解密之后再返回:

spring.shardingsphere.props.query.with.cipher.comlum=true

執行數據脫敏

現在,配置工作一切就緒,我們來執行測試用例。首先執行數據插入操作,下圖數據表中對應字段存儲的就是加密后的密文數據:

image-20201118210419846

加密后的表數據結果

在這個過程中,ShardingSphere 會把原始的 SQL 語句轉換為用於數據脫敏的目標語句:

image-20201118210455127

可以看到這里的路由類型為“encrypt”,獲取的 user_name 是經過解密之后的明文而不是數據庫中存儲的密文,這就是 spring.shardingsphere.props.query.with.cipher.comlum=true 配置項所起到的作用。如果將這個配置項設置為 false,那么返回的就是密文。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM