前言:
利用数据库视图,实现web接口查询敏感信息时动态脱敏。
具体目标:某接口为用户信息查询接口,返回敏感用户信息(id,姓名、手机号【敏感】、身份证号【敏感】),如果web用户为管理员角色,则查询后返回明文用户信息,如果用户为普通用户信息,则查询后返回脱敏后的用户信息。
具体步骤:
一、在mysql中新建一个用户信息表,并添加几条数据
二、对上表创建脱敏后的视图,利用掩码技术脱敏,脱敏字段为phone和id_card
create view user_info_view as select id,name,concat(left(phone,3),'****',right(phone,3)) as phone,concat(left(id_card,4),'**************') as id_card from user_info;
三、SpringBoot项目启用SpringSecurity,在配置文件中,内存新建账号的时候添加admin和normal两个角色
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
package Eleven.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("123456")).roles("admin"); auth.inMemoryAuthentication().withUser("user").password(passwordEncoder().encode("123456")).roles("normal"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护 .antMatchers("/login").permitAll()// 设置所有人都可以访问登录页面 .anyRequest().authenticated() // 任何请求,登录后可以访问 .and() .formLogin().loginPage("/login") ; } }
四、创建UserInfo的domain类
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
package Eleven.domain; public class UserInfo { private long id; private String name; private String phone; private String id_card; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getId_card() { return id_card; } public void setId_card(String id_card) { this.id_card = id_card; } }
五、创建Mapper,访问数据库的接口,两个查询方法,以便按不同的角色区分查询原始表还是脱敏后的视图
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
package Eleven.mapper; import Eleven.domain.UserInfo; import org.apache.ibatis.annotations.*; @Mapper public interface UserMapper { //根据用户名查询,查询原始表,明文显示敏感信息 @Select("select * from user_info where name=#{name}") UserInfo findByName(String name); //根据用户名查询,查询脱敏后的视图表,脱敏显示敏感信息 @Select("select * from user_info_view where name=#{name}") UserInfo findByNameSec(String name); }
六、创建Service 和Impl文件
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
package Eleven.service; import Eleven.domain.UserInfo; public interface UserService { public UserInfo find(String name); public UserInfo findSec(String name); }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
package Eleven.impl; import Eleven.domain.UserInfo; import Eleven.mapper.UserMapper; import Eleven.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public UserInfo find(String name){ return userMapper.findByName(name); } public UserInfo findSec(String name){ return userMapper.findByNameSec(name); } }
七、创建controller文件,其中Get请求中,获取登录用户的角色,不同的角色调用Service中不同的函数,最终admin用户在数据库原始表中执行SQL查询,普通用户在脱敏后的视图中执行SQL查询
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
package Eleven.controller; import Eleven.domain.User; import Eleven.domain.UserInfo; import Eleven.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @Autowired private UserService userService; @GetMapping("/findByName") public Object findByName(String name){ /** * 获取登录用户的角色,不同角色调用Service中不同的函数,最终执行不同的Sql查询 */ Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth.getAuthorities().toString().equals("[ROLE_admin]")){ UserInfo userInfo = userService.find(name); return userInfo; } else if (auth.getAuthorities().toString().equals("[ROLE_normal]")){ UserInfo userInfo = userService.findSec(name); return userInfo; } else { return auth.getAuthorities().toString(); } } }
八、测试验证
1、访问SpringBoot WEB,进入登录页面
2、使用admin用户登录后,访问查询用户信息接口,如下图所示,返回的是明文用户信息
3、使用user普通用户登录后访问,如下图所示,返回的是脱敏后的用户信息
利用数据库视图技术实现动态脱敏,适用于web应用,适用于生产环境,可按用户角色返回脱敏前后的查询结果。但是所有敏感数据表均需生成视图,额外占用数据库存储空间;为了实现目标,研发人员需要额外的开发工作量。