数据权限过滤注解

根据权限去定义能看到那些数据,自定义注解和切面,动态修改 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映射
#指定MybatisMapper文件
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, "");
        }
    }
}


在这里插入图片描述

🍉 部门权限

controllerservice,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);
}

mapperxml 文件:

<?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>


在这里插入图片描述

🍉 角色权限

controllerservice,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);
}

mapperxml 文件:

<?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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光头小小强007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值