根据权限去定义能看到那些数据,自定义注解和切面,动态修改 sql
进行数据查询。
🍉 环境搭建
新建项目并添加依赖:
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!-- mybatis-plus 生成工具 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<!--spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- freemaker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
application.properties
配置:
# 应用名称
spring.application.name=data_scope
# 应用服务 WEB 访问端口
server.port=8080
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
#mybatis.type-aliases-package=org.javaboy.ds.mybatis.entity
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
#spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://192.168.10.10:3306/ry-vue?serverTimezone=Asia/Shanghai&useSSL=false
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
配置 mybatis-plus
代码生成器:
@SpringBootTest
class DataScopeApplicationTests {
@Test
void contextLoads() {
FastAutoGenerator.create("jdbc:mysql://192.168.10.10:3306/ry-vue?serverTimezone=Asia/Shanghai&useSSL=false", "root", "root")
.globalConfig(builder -> {
builder.author("javaboy") // 设置作者
.fileOverride() // 覆盖已生成文件
.outputDir("D:\\workspace\\ruoyi_test\\data_scope\\src\\main\\java"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("org.javaboy.ds") // 设置父包名
// .moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, "D:\\workspace\\ruoyi_test\\data_scope\\src\\main\\resources\\mapper")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("sys_dept","sys_role","sys_user") // 设置需要生成的表名
.addTablePrefix("sys_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
user
用户:
@TableName("sys_user")
public class User extends BaseEntity implements Serializable, UserDetails {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@TableId(value = "user_id", type = IdType.AUTO)
private Long userId;
/**
* 部门ID
*/
private Long deptId;
/**
* 用户账号
*/
private String userName;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户类型(00系统用户)
*/
private String userType;
/**
* 用户邮箱
*/
private String email;
/**
* 手机号码
*/
private String phonenumber;
/**
* 用户性别(0男 1女 2未知)
*/
private String sex;
/**
* 头像地址
*/
private String avatar;
/**
* 密码
*/
private String password;
/**
* 帐号状态(0正常 1停用)
*/
private String status;
/**
* 删除标志(0代表存在 2代表删除)
*/
private String delFlag;
/**
* 最后登录IP
*/
private String loginIp;
/**
* 最后登录时间
*/
private LocalDateTime loginDate;
/**
* 创建者
*/
private String createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新者
*/
private String updateBy;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 备注
*/
private String remark;
@TableField(exist = false)
public List<Role> roles;
public static long getSerialVersionUID() {
return serialVersionUID;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getDeptId() {
return deptId;
}
public void setDeptId(Long deptId) {
this.deptId = deptId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhonenumber() {
return phonenumber;
}
public void setPhonenumber(String phonenumber) {
this.phonenumber = phonenumber;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
@Override
// 忽略生成 json
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> collect = roles.stream().map(r -> new SimpleGrantedAuthority(r.getRoleKey())).collect(Collectors.toList());
return collect ;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return userName;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public void setPassword(String password) {
this.password = password;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getDelFlag() {
return delFlag;
}
public void setDelFlag(String delFlag) {
this.delFlag = delFlag;
}
public String getLoginIp() {
return loginIp;
}
public void setLoginIp(String loginIp) {
this.loginIp = loginIp;
}
public LocalDateTime getLoginDate() {
return loginDate;
}
public void setLoginDate(LocalDateTime loginDate) {
this.loginDate = loginDate;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", deptId=" + deptId +
", userName=" + userName +
", nickName=" + nickName +
", userType=" + userType +
", email=" + email +
", phonenumber=" + phonenumber +
", sex=" + sex +
", avatar=" + avatar +
", password=" + password +
", status=" + status +
", delFlag=" + delFlag +
", loginIp=" + loginIp +
", loginDate=" + loginDate +
", createBy=" + createBy +
", createTime=" + createTime +
", updateBy=" + updateBy +
", updateTime=" + updateTime +
", remark=" + remark +
"}";
}
}
role
角色:
@TableName("sys_dept")
public class Dept extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 部门id
*/
@TableId(value = "dept_id", type = IdType.AUTO)
private Long deptId;
/**
* 父部门id
*/
private Long parentId;
/**
* 祖级列表
*/
private String ancestors;
/**
* 部门名称
*/
private String deptName;
/**
* 显示顺序
*/
private Integer orderNum;
/**
* 负责人
*/
private String leader;
/**
* 联系电话
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 部门状态(0正常 1停用)
*/
private String status;
/**
* 删除标志(0代表存在 2代表删除)
*/
private String delFlag;
/**
* 创建者
*/
private String createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新者
*/
private String updateBy;
/**
* 更新时间
*/
private LocalDateTime updateTime;
// 省略 get/set/toString 方法
}
dept
部门:
@TableName("sys_dept")
public class Dept extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 部门id
*/
@TableId(value = "dept_id", type = IdType.AUTO)
private Long deptId;
/**
* 父部门id
*/
private Long parentId;
/**
* 祖级列表
*/
private String ancestors;
/**
* 部门名称
*/
private String deptName;
/**
* 显示顺序
*/
private Integer orderNum;
/**
* 负责人
*/
private String leader;
/**
* 联系电话
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 部门状态(0正常 1停用)
*/
private String status;
/**
* 删除标志(0代表存在 2代表删除)
*/
private String delFlag;
/**
* 创建者
*/
private String createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新者
*/
private String updateBy;
/**
* 更新时间
*/
private LocalDateTime updateTime;
// 省略 get/set/toString 方法
}
重写 UserService
方法:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService, UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUserName,s);
User user = this.baseMapper.selectOne(wrapper);
if (ObjectUtils.isEmpty(user)){
throw new UsernameNotFoundException("用户不存在!");
}
return user;
}
}
配置 spring security
:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceImpl userService;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
}
🍉 功能实现
创建一个 BaseEtity
的基类,所有的 model
继承该基类。需要追加的 sql
存放在 params
参数中。
/**
* @author: yueLQ
* @date: 2022-09-27 20:23
*
* 需要追加的 sql 放到 params 中去
*/
public class BaseEntity {
@TableField(exist = false)
private Map<String,String> params = new HashMap<>();
public Map<String, String> getParams() {
return params;
}
public void setParams(Map<String, String> params) {
this.params = params;
}
}
定义 @dataScope
注解,在需要进行权限的地方标注:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataScope {
/**
* 部门
* @return
*/
String deptAlias() default "";
/**
* 用户
* @return
*/
String userAlias() default "";
}
创建切面,进行业务上的权限处理,本质上是进行 sql
拼接,对数据进行处理:
@Component
@Aspect
public class DataScopeAspect {
//所有去权限
public static final String DATA_SCOPE_ALL = "1";
// 自定义权限
public static final String DATA_SCOPE_CUSTOM = "2";
// 部门权限
public static final String DATA_SCOPE_DEPT = "3";
// 部门以及其下属
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
// 自己
public static final String DATA_SCOPE_SELF = "5";
// params 中传递的参数
public static final String DATA_SCOPE = "data_scope";
@Before("@annotation(dataScope)")
public void doBefore(JoinPoint point, DataScope dataScope) {
// 清除 params 中的数据,防止接口调用时候传入参数
clearDataScope(point);
// 获取当前登录用户信息
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (user.getUserId() == 1L) {
// 说明是超级管理员,不用进行权限过过滤
return;
}
// 进行现有 sql 进行追加
StringBuilder sql = new StringBuilder();
// 获取角色
// select * from sys_dept d where d.del_flag='0'
// and dept_id in (select rd.dept_id from sys_role_dept rd where
// rd.role_id=2 )
List<Role> roles = user.getRoles();
for (Role role : roles) {
// 获取角色的数据权限
String scope = role.getDataScope();
switch (scope){
case DATA_SCOPE_ALL:
// 如果用户能查所有数据,这里不需要做什么
return;
case DATA_SCOPE_CUSTOM:
// 自定义数据权限,那么就根据用户角色,去查找到部门 id
sql.append(String
.format("OR %s.dept_id in(select rd.dept_id from sys_role_dept rd where rd.role_id=%d)"
,dataScope.deptAlias(),role.getRoleId()));
break;
case DATA_SCOPE_DEPT:
sql.append(String.format("OR %s.dept_id=%d",dataScope.deptAlias(),user.getDeptId()));
break;
case DATA_SCOPE_DEPT_AND_CHILD:
sql.append(String.format("OR %s.dept_id in (select dept_id from sys_dept WHERE dept_id=%d or FIND_IN_SET(%d,`ancestors`))"
,dataScope.deptAlias()
,user.getDeptId(),user.getDeptId()));
break;
case DATA_SCOPE_SELF:
String userAlias = dataScope.userAlias();
if ("".equals(userAlias)){
// 数据权限仅限于本人
sql.append("OR 1=0");
}else {
sql.append(String.format("OR %s.user_id=%d",dataScope.userAlias(),user.getUserId()));
}
break;
default:
break;
}
}
// and (xxx or xxx or xxx)
Object arg = point.getArgs()[0];
if (arg!=null&&arg instanceof BaseEntity){
BaseEntity entity = (BaseEntity) arg;
entity.getParams().put(DATA_SCOPE,"AND ("+sql.substring(3)+")");
}
}
/**
* 如果 params 中已经有参数了,则删除掉,防止 sql 注入
*
* @param point
*/
private void clearDataScope(JoinPoint point) {
Object arg = point.getArgs()[0];
if (arg != null && arg instanceof BaseEntity) {
BaseEntity baseEntity = (BaseEntity) arg;
baseEntity.getParams().put(DATA_SCOPE, "");
}
}
}
🍉 部门权限
controller
、service
,mapper
…
// controller
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
IDeptService deptService;
@DataScope(deptAlias = "d")
@GetMapping("/")
public List<Dept> getAllDepts(Dept dept){
return deptService.getAllDepts(dept);
}
}
// serviceImpl
public interface IDeptService extends IService<Dept> {
List<Dept> getAllDepts(Dept dept);
}
// service
@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements IDeptService {
@Autowired
private DeptMapper deptMapper;
@Override
public List<Dept> getAllDepts(Dept dept) {
return deptMapper.getAllDepts(dept);
}
}
// mapper
public interface DeptMapper extends BaseMapper<Dept> {
List<Dept> getAllDepts(Dept dept);
}
mapper
的 xml
文件:
<?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="org.javaboy.ds.mapper.DeptMapper">
<select id="getAllDepts" resultType="org.javaboy.ds.entity.Dept">
select * from sys_dept d where d.del_flag='0'
${params.data_scope}
</select>
</mapper>
🍉 角色权限
controller
、service
,mapper
…
// controller
@RestController
@RequestMapping("/role")
public class RoleController {
@Autowired
IRoleService iRoleService;
@GetMapping("/")
@DataScope(deptAlias = "d",userAlias = "u")
public List<Role> getAllRoles(Role role){
return iRoleService.getAllRoles(role);
}
}
// serviceImpl
public interface IRoleService extends IService<Role> {
List<Role> getAllRoles(Role role);
}
// service
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IRoleService {
@Autowired
private RoleMapper roleMapper;
@Override
public List<Role> getAllRoles(Role role) {
return roleMapper.getAllRoles(role);
}
}
// mapper
public interface RoleMapper extends BaseMapper<Role> {
List<Role> getAllRoles(Role role);
}
mapper
的 xml
文件:
<?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="org.javaboy.ds.mapper.RoleMapper">
<select id="getAllRoles" resultType="org.javaboy.ds.entity.Role">
select r.*
from sys_role r
left join sys_user_role ur on r.role_id=ur.role_id
left join sys_user u on ur.user_id=u.user_id
left join sys_dept d on u.dept_id=d.dept_id where r.del_flag='0'
${params.data_scope}
</select>
</mapper>