mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
1109 lines
34 KiB
HTML
1109 lines
34 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>10 数据脱敏:如何确保敏感数据的安全访问?.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/00 如何正确学习一款分库分表开源框架?.md.html">00 如何正确学习一款分库分表开源框架?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/01 从理论到实践:如何让分库分表真正落地?.md.html">01 从理论到实践:如何让分库分表真正落地?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/02 顶级项目:ShardingSphere 是一款什么样的 Apache 开源软件?.md.html">02 顶级项目:ShardingSphere 是一款什么样的 Apache 开源软件?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/03 规范兼容:JDBC 规范与 ShardingSphere 是什么关系?.md.html">03 规范兼容:JDBC 规范与 ShardingSphere 是什么关系?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/04 应用集成:在业务系统中使用 ShardingSphere 的方式有哪些?.md.html">04 应用集成:在业务系统中使用 ShardingSphere 的方式有哪些?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/05 配置驱动:ShardingSphere 中的配置体系是如何设计的?.md.html">05 配置驱动:ShardingSphere 中的配置体系是如何设计的?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/06 数据分片:如何实现分库、分表、分库+分表以及强制路由?(上).md.html">06 数据分片:如何实现分库、分表、分库+分表以及强制路由?(上).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/07 数据分片:如何实现分库、分表、分库+分表以及强制路由?(下).md.html">07 数据分片:如何实现分库、分表、分库+分表以及强制路由?(下).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/08 读写分离:如何集成分库分表+数据库主从架构?.md.html">08 读写分离:如何集成分库分表+数据库主从架构?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/09 分布式事务:如何使用强一致性事务与柔性事务?.md.html">09 分布式事务:如何使用强一致性事务与柔性事务?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/ShardingSphere 核心原理精讲-完/10 数据脱敏:如何确保敏感数据的安全访问?.md.html">10 数据脱敏:如何确保敏感数据的安全访问?.md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/11 编排治理:如何实现分布式环境下的动态配置管理?.md.html">11 编排治理:如何实现分布式环境下的动态配置管理?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/12 从应用到原理:如何高效阅读 ShardingSphere 源码?.md.html">12 从应用到原理:如何高效阅读 ShardingSphere 源码?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/13 微内核架构:ShardingSphere 如何实现系统的扩展性?.md.html">13 微内核架构:ShardingSphere 如何实现系统的扩展性?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/14 分布式主键:ShardingSphere 中有哪些分布式主键实现方式?.md.html">14 分布式主键:ShardingSphere 中有哪些分布式主键实现方式?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/15 解析引擎:SQL 解析流程应该包括哪些核心阶段?(上).md.html">15 解析引擎:SQL 解析流程应该包括哪些核心阶段?(上).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/16 解析引擎:SQL 解析流程应该包括哪些核心阶段?(下).md.html">16 解析引擎:SQL 解析流程应该包括哪些核心阶段?(下).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/17 路由引擎:如何理解分片路由核心类 ShardingRouter 的运作机制?.md.html">17 路由引擎:如何理解分片路由核心类 ShardingRouter 的运作机制?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/18 路由引擎:如何实现数据访问的分片路由和广播路由?.md.html">18 路由引擎:如何实现数据访问的分片路由和广播路由?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/19 路由引擎:如何在路由过程中集成多种路由策略和路由算法?.md.html">19 路由引擎:如何在路由过程中集成多种路由策略和路由算法?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/20 改写引擎:如何理解装饰器模式下的 SQL 改写实现机制?.md.html">20 改写引擎:如何理解装饰器模式下的 SQL 改写实现机制?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/21 执行引擎:分片环境下 SQL 执行的整体流程应该如何进行抽象?.md.html">21 执行引擎:分片环境下 SQL 执行的整体流程应该如何进行抽象?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/22 执行引擎:如何把握 ShardingSphere 中的 Executor 执行模型?(上).md.html">22 执行引擎:如何把握 ShardingSphere 中的 Executor 执行模型?(上).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/23 执行引擎:如何把握 ShardingSphere 中的 Executor 执行模型?(下).md.html">23 执行引擎:如何把握 ShardingSphere 中的 Executor 执行模型?(下).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/24 归并引擎:如何理解数据归并的类型以及简单归并策略的实现过程?.md.html">24 归并引擎:如何理解数据归并的类型以及简单归并策略的实现过程?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/25 归并引擎:如何理解流式归并和内存归并在复杂归并场景下的应用方式?.md.html">25 归并引擎:如何理解流式归并和内存归并在复杂归并场景下的应用方式?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/26 读写分离:普通主从架构和分片主从架构分别是如何实现的?.md.html">26 读写分离:普通主从架构和分片主从架构分别是如何实现的?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/27 分布式事务:如何理解 ShardingSphere 中对分布式事务的抽象过程?.md.html">27 分布式事务:如何理解 ShardingSphere 中对分布式事务的抽象过程?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/28 分布式事务:ShardingSphere 中如何集成强一致性事务和柔性事务支持?(上).md.html">28 分布式事务:ShardingSphere 中如何集成强一致性事务和柔性事务支持?(上).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/29 分布式事务:ShardingSphere 中如何集成强一致性事务和柔性事务支持?(下).md.html">29 分布式事务:ShardingSphere 中如何集成强一致性事务和柔性事务支持?(下).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/30 数据脱敏:如何基于改写引擎实现低侵入性数据脱敏方案?.md.html">30 数据脱敏:如何基于改写引擎实现低侵入性数据脱敏方案?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/31 配置中心:如何基于配置中心实现配置信息的动态化管理?.md.html">31 配置中心:如何基于配置中心实现配置信息的动态化管理?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/32 注册中心:如何基于注册中心实现数据库访问熔断机制?.md.html">32 注册中心:如何基于注册中心实现数据库访问熔断机制?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/33 链路跟踪:如何基于 Hook 机制以及 OpenTracing 协议实现数据访问链路跟踪?.md.html">33 链路跟踪:如何基于 Hook 机制以及 OpenTracing 协议实现数据访问链路跟踪?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/34 系统集成:如何完成 ShardingSphere 内核与 Spring+SpringBoot 的无缝整合?.md.html">34 系统集成:如何完成 ShardingSphere 内核与 Spring+SpringBoot 的无缝整合?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/35 结语:ShardingSphere 总结及展望.md.html">35 结语:ShardingSphere 总结及展望.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>10 数据脱敏:如何确保敏感数据的安全访问?</h1>
|
||
|
||
<p>从今天开始,我们又将开始一个全新的主题:介绍 ShardingSphere 中的数据脱敏功能。所谓数据脱敏,是指对某些敏感信息通过脱敏规则进行数据转换,从而实现敏感隐私数据的可靠保护。在日常开发过程中,数据安全一直是一个非常重要和敏感的话题。相较传统的私有化部署方案,互联网应用对数据安全的要求更高,所涉及的范围也更广。根据不同行业和业务场景的属性,不同系统的敏感信息可能有所不同,但诸如身份证号、手机号、卡号、用户姓名、账号密码等个人信息一般都需要进行脱敏处理。</p>
|
||
|
||
<h3>ShardingSphere 如何抽象数据脱敏?</h3>
|
||
|
||
<p>数据脱敏从概念上讲比较容易理解,但在具体实现过程中存在很多方案。在介绍基于数据脱敏的具体开发过程之前,我们有必要先来梳理实现数据脱敏的抽象过程。这里,我将从敏感数据的存储方式、敏感数据的加解密过程以及在业务代码中嵌入加解密的过程这三个维度来抽象数据脱敏。</p>
|
||
|
||
<p><img src="assets/CgqCHl8P-QmAA0bQAABWInFwGYE998.png" alt="Drawing 0.png" /></p>
|
||
|
||
<p>针对每一个维度,我也将基于 ShardingSphere 给出这个框架的具体抽象过程,从而方便你理解使用它的方法和技巧,让我们来一起看一下。</p>
|
||
|
||
<h4>敏感数据如何存储?</h4>
|
||
|
||
<p>关于这个问题,要讨论的点在于是否需要将敏感数据以明文形式存储在数据库中。这个问题的答案并不是绝对的。</p>
|
||
|
||
<p>我们先来考虑第一种情况。<strong>对于一些敏感数据而言,我们显然应该直接以密文的形式将加密之后的数据进行存储,防止有任何一种途径能够从数据库中获取这些数据明文。</strong> 在这类敏感数据中,最典型的就是用户密码,我们通常会采用 MD5 等不可逆的加密算法对其进行加密,而使用这些数据的方法也只是依赖于它的密文形式,不会涉及对明文的直接处理。</p>
|
||
|
||
<p><strong>但对于用户姓名、手机号等信息,由于统计分析等方面的需要,显然我们不能直接采用不可逆的加密算法对其进行加密,还需要将明文信息进行处理</strong>**。**一种常见的处理方式是将一个字段用两列来进行保存,一列保存明文,一列保存密文,这就是第二种情况。</p>
|
||
|
||
<p>显然,我们可以将第一种情况看作是第二种情况的特例。也就是说,在第一种情况中没有明文列,只有密文列。</p>
|
||
|
||
<p>ShardingSphere 同样基于这两种情况进行了抽象,它将这里的明文列命名为 plainColumn,而将密文列命名为 cipherColumn。其中 plainColumn 属于选填,而 cipherColumn 则是必填。同时,ShardingSphere 还提出了一个逻辑列 logicColumn 的概念,该列代表一种虚拟列,只面向开发人员进行编程使用:</p>
|
||
|
||
<p><img src="assets/CgqCHl8P-SWAcpV1AABNv8n4KHg426.png" alt="Drawing 2.png" /></p>
|
||
|
||
<h4>敏感数据如何加解密?</h4>
|
||
|
||
<p>数据脱敏本质上就是一种加解密技术应用场景,自然少不了对各种加解密算法和技术的封装。<strong>传统的加解密方式有两种,一种是对称加密,常见的包括 DEA 和 AES;另一种是非对称加密,常见的包括 RSA。</strong></p>
|
||
|
||
<p>ShardingSphere 内部也抽象了一个 ShardingEncryptor 组件专门封装各种加解密操作:</p>
|
||
|
||
<pre><code>public interface ShardingEncryptor extends TypeBasedSPI {
|
||
|
||
//初始化
|
||
|
||
void init();
|
||
|
||
//加密
|
||
|
||
String encrypt(Object plaintext);
|
||
|
||
//解密
|
||
|
||
Object decrypt(String ciphertext);
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>目前,ShardingSphere 内置了 AESShardingEncryptor 和 MD5ShardingEncryptor 这两个具体的 ShardingEncryptor 实现。当然,由于 ShardingEncryptor 扩展了 TypeBasedSPI 接口,所以开发人员完全可以基于微内核架构和 JDK 所提供的 SPI 机制来实现和动态加载自定义的各种 ShardingEncryptor。我们会在“微内核架构:ShardingSphere 如何实现系统的扩展性?”这个课时中对 ShardingSphere 中的微内核架构和 SPI 机制进行详细的讨论。</p>
|
||
|
||
<h4>业务代码中如何嵌入数据脱敏?</h4>
|
||
|
||
<p>数据脱敏的最后一个抽象点在于如何在业务代码中嵌入数据脱敏过程,显然这个过程应该尽量做到自动化,并且具备低侵入性,且应该对开发人员足够透明。</p>
|
||
|
||
<p>我们可以通过一个具体的示例来描述数据脱敏的执行流程。假设系统中存在一张 user 表,其中包含一个 user_name 列。我们认为这个 user_name 列属于敏感数据,需要对其进行数据脱敏。那么按照前面讨论的数据存储方案,可以在 user 表中设置两个字段,一个代表明文的 user_name_plain,一个代表密文的 user_name_cipher。然后应用程序通过 user_name 这个逻辑列与数据库表进行交互:</p>
|
||
|
||
<p><img src="assets/Ciqc1F8P-TaAd-1QAABkT9WjY8E581.png" alt="Drawing 4.png" /></p>
|
||
|
||
<p>针对这个交互过程,我们希望存在一种机制,能够自动将 user_name 逻辑列映射到 user_name_plain 和 user_name_cipher 列。同时,我们希望提供一种配置机制,能够让开发人员根据需要灵活指定脱敏过程中所采用的各种加解密算法。</p>
|
||
|
||
<p>作为一款优秀的开源框架,ShardingSphere 就提供了这样一种机制。那么它是如何做到这一点呢?</p>
|
||
|
||
<p>首先,ShardingSphere 通过对从应用程序传入的 SQL 进行解析,并依据开发人员提供的脱敏配置对 SQL 进行改写,从而实现对明文数据的自动加密,并将加密后的密文数据存储到数据库中。当我们查询数据时,它又从数据库中取出密文数据,并自动对其解密,最终将解密后的明文数据返回给用户。ShardingSphere 提供了自动化+透明化的数据脱敏过程,业务开发人员可以像使用普通数据那样使用脱敏数据,而不需要关注数据脱敏的实现细节。</p>
|
||
|
||
<h3>系统改造:如何实现数据脱敏?</h3>
|
||
|
||
<p>接下来,就让我们继续对系统进行改造,并添加数据脱敏功能吧。这个过程主要有三个步骤:准备数据脱敏、配置数据脱敏和执行数据脱敏。</p>
|
||
|
||
<h4>准备数据脱敏</h4>
|
||
|
||
<p>为了演示数据脱敏功能,我们重新定义一个 EncryptUser 实体类,该类中定义了与数据脱敏相关的常见用户名、密码等字段,这些字段与数据库中 encrypt_user 表的列是一一对应的:</p>
|
||
|
||
<pre><code>public class EncryptUser {
|
||
|
||
//用户Id
|
||
|
||
private Long userId;
|
||
|
||
//用户名(密文)
|
||
|
||
private String userName;
|
||
|
||
//用户名(明文)
|
||
|
||
private String userNamePlain;
|
||
|
||
//密码(密文)
|
||
|
||
private String pwd;
|
||
|
||
…
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>接下来,我们有必要提一下 EncryptUserMapper 中关于 resultMap 和 insert 语句的定义,如下所示:</p>
|
||
|
||
<pre><code><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"/>
|
||
|
||
</resultMap>
|
||
|
||
<insert id="addEntity">
|
||
|
||
INSERT INTO encrypt_user (user_id, user_name, pwd) VALUES (#{userId,jdbcType=INTEGER}, #{userName,jdbcType=VARCHAR}, #{pwd,jdbcType=VARCHAR})
|
||
|
||
</insert>
|
||
|
||
…
|
||
|
||
</mapper>
|
||
|
||
</code></pre>
|
||
|
||
<p>请注意,<strong>我们在 resultMap 中并没有指定 user_name_plain 字段,同时,insert 语句中同样没有指定这个字段。</strong></p>
|
||
|
||
<p>有了 Mapper,我们就可以构建 Service 层组件。在这个 EncryptUserServiceImpl 类中,我们分别提供了 processEncryptUsers 和 getEncryptUsers 方法来插入用户以及获取用户列表。</p>
|
||
|
||
<pre><code>@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("username_" + i);
|
||
|
||
encryptUser.setPwd("pwd" + i);
|
||
|
||
encryptUserRepository.addEntity(encryptUser);
|
||
|
||
result.add(encryptUser.getUserId());
|
||
|
||
}
|
||
|
||
|
||
|
||
return result;
|
||
|
||
}
|
||
|
||
@Override
|
||
|
||
public List<EncryptUser> getEncryptUsers() throws SQLException {
|
||
|
||
return encryptUserRepository.findEntities();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>现在,业务层代码已经准备就绪。由于数据脱敏功能内嵌在 sharding-jdbc-spring-boot-starter 中,所以我们不需要引入额外的依赖包。</p>
|
||
|
||
<h4>配置数据脱敏</h4>
|
||
|
||
<p>在整体架构上,和分库分表以及读写分离一样,数据脱敏对外暴露的入口也是一个符合 JDBC 规范的 EncryptDataSource 对象。如下面的代码所示,ShardingSphere 提供了 EncryptDataSourceFactory 工厂类,完成了 EncryptDataSource 对象的构建:</p>
|
||
|
||
<pre><code>public final class EncryptDataSourceFactory {
|
||
|
||
|
||
|
||
public static DataSource createDataSource(final DataSource dataSource, final EncryptRuleConfiguration encryptRuleConfiguration, final Properties props) throws SQLException {
|
||
|
||
return new EncryptDataSource(dataSource, new EncryptRule(encryptRuleConfiguration), props);
|
||
|
||
}
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>可以看到,这里存在一个 EncryptRuleConfiguration 类,该类中包含了两个 Map,分别用来配置加解密器列表以及加密表配置列表:</p>
|
||
|
||
<pre><code>//加解密器配置列表
|
||
|
||
private final Map<String, EncryptorRuleConfiguration> encryptors;
|
||
|
||
//加密表配置列表
|
||
|
||
private final Map<String, EncryptTableRuleConfiguration> tables;
|
||
|
||
</code></pre>
|
||
|
||
<p>其中 EncryptorRuleConfiguration 集成了 ShardingSphere 中的一个通用抽象类 TypeBasedSPIConfiguration,包含了 type 和 properties 这两个字段:</p>
|
||
|
||
<pre><code> //类型(例如MD5/AES加密器)
|
||
|
||
private final String type;
|
||
|
||
//属性(例如AES加密器用到的Key值)
|
||
|
||
private final Properties properties;
|
||
|
||
</code></pre>
|
||
|
||
<p>而 EncryptTableRuleConfiguration 内部是一个包含多个 EncryptColumnRuleConfiguration 的 Map,这个 EncryptColumnRuleConfiguration 就是 ShardingSphere 中对加密列的配置,包含了 plainColumn、cipherColumn 的定义:</p>
|
||
|
||
<pre><code>public final class EncryptColumnRuleConfiguration {
|
||
|
||
//存储明文的字段
|
||
|
||
private final String plainColumn;
|
||
|
||
//存储密文的字段
|
||
|
||
private final String cipherColumn;
|
||
|
||
//辅助查询字段
|
||
|
||
private final String assistedQueryColumn;
|
||
|
||
//加密器名字
|
||
|
||
private final String encryptor;
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>作为总结,我们通过一张图罗列出各个配置类之间的关系,以及数据脱敏所需要配置的各项内容:</p>
|
||
|
||
<p><img src="assets/Ciqc1F8P-VqAZq9CAACLcF2qedw534.png" alt="Drawing 6.png" /></p>
|
||
|
||
<p>现在回到代码,为了实现数据脱敏,我们首先需要定义一个数据源,这里命名为 dsencrypt:</p>
|
||
|
||
<pre><code>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
|
||
|
||
</code></pre>
|
||
|
||
<p>配置成功之后,我们再配置加密器,这里定义 name_encryptor 和 pwd_encryptor 这两个加密器,分别用于对 user_name 列和 pwd 列进行加解密。注意,在下面这段代码中,对于 name_encryptor,我们使用了对称加密算法 AES;而对于 pwd_encryptor,我们则直接使用不可逆的 MD5 散列算法:</p>
|
||
|
||
<pre><code>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
|
||
|
||
</code></pre>
|
||
|
||
<p>接下来,我们需要完成脱敏表的配置。针对案例中的场景,我们可以选择对 user_name 列设置 plainColumn、cipherColumn 以及 encryptor 属性,而对于 pwd 列而言,由于我们不希望在数据库中存储明文,所以只需要配置 cipherColumn 和 encryptor 属性就可以了。</p>
|
||
|
||
<pre><code>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
|
||
|
||
</code></pre>
|
||
|
||
<p>最后,ShardingSphere 还提供了一个属性开关,当底层数据库表里同时存储了明文和密文数据后,该属性开关可以决定是直接查询数据库表里的明文数据进行返回,还是查询密文数据并进行解密之后再返回:</p>
|
||
|
||
<pre><code>spring.shardingsphere.props.query.with.cipher.comlum=true
|
||
|
||
</code></pre>
|
||
|
||
<h4>执行数据脱敏</h4>
|
||
|
||
<p>现在,配置工作一切就绪,我们来执行测试用例。首先执行数据插入操作,下图数据表中对应字段存储的就是加密后的密文数据:</p>
|
||
|
||
<p><img src="assets/CgqCHl8P-WeAZFtRAABT51HN_2s801.png" alt="Drawing 8.png" />
|
||
|
||
加密后的表数据结果</p>
|
||
|
||
<p>在这个过程中,ShardingSphere 会把原始的 SQL 语句转换为用于数据脱敏的目标语句:</p>
|
||
|
||
<p><img src="assets/Ciqc1F8P-W6AVpohAAA833UHvZE135.png" alt="Drawing 9.png" />
|
||
|
||
SQL 自动转换示意图</p>
|
||
|
||
<p>然后,我们再来执行查询语句并获取控制台日志:</p>
|
||
|
||
<pre><code>2020-05-30 15:10:59.174 INFO 31808 --- [ main] ShardingSphere-SQL : Rule Type: encrypt
|
||
|
||
2020-05-30 15:10:59.174 INFO 31808 --- [ main] ShardingSphere-SQL : SQL: SELECT * FROM encrypt_user;
|
||
|
||
user_id: 1, user_name: username_1, pwd: 99024280cab824efca53a5d1341b9210
|
||
|
||
user_id: 2, user_name: username_2, pwd: 36ddda5af915d91549d3ab5bff1bafec
|
||
|
||
…
|
||
|
||
</code></pre>
|
||
|
||
<p>可以看到这里的路由类型为“encrypt”,获取的 user_name 是经过解密之后的明文而不是数据库中存储的密文,这就是 spring.shardingsphere.props.query.with.cipher.comlum=true 配置项所起到的作用。如果将这个配置项设置为 false,那么返回的就是密文。</p>
|
||
|
||
<h3>总结</h3>
|
||
|
||
<p>数据脱敏是数据库管理和数据访问控制的一个重要话题,今天我们讲解了 ShardingSphere 在数据脱敏方面提供的技术方案,但实际上,数据脱敏的实现思路有很多,ShardingSphere 采用了一种自动化、透明化的方案完成敏感数据存储、加解密以及和应用程序之间的无缝整合。同时,今天的课程也围绕系统案例对其进行了数据库脱敏改造,我们给出了具体的配置项和执行过程。</p>
|
||
|
||
<p>这里给你留一道思考题:当使用 ShardingSphere 的数据脱敏模块时,我们有哪几种方式可以设置一个需要加密的数据项?</p>
|
||
|
||
<p>本课时的内容就到这里,在下一课时中,我们将介绍 ShardingSphere 中与编排治理相关的辅助功能,将重点围绕配置中心展开分析。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/09 分布式事务:如何使用强一致性事务与柔性事务?.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/ShardingSphere 核心原理精讲-完/11 编排治理:如何实现分布式环境下的动态配置管理?.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709974b28e463d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
|
||
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
|
||
|
||
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
|
||
|
||
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
|
||
|
||
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
</html>
|
||
|