前言

之前都是使用若依框架来实现的动态权限和菜单功能,但是一直想尝试自己来实现动态权限。所以这两天准备整合一下自己的所学知识,依据RBAC权限模型,使用SpringBoot+SpringSecurity+Vue来自己实现一下动态权限。

数据库结构

在数据库设计方面是根据RBAC权限模型来设计的,分别有user(用户)表,role(角色)表,permission(权限)表,user_role(用户角色)表和role_permission(角色权限)表。大致字段如下:
在这里插入图片描述

技术选型

为了方便开发,选择了市面上主流的框架SpringBoot+Vue,安全框架使用SpringSecurity,ORM框架使用MyBatis-Plus。缓存使用Redis。

构建项目

构建一个普通的SpringBoot项目,在pom文件导入以下依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.8</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lyx.autoperm</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>autoperm</name>
    <description>配合SpringSecurity动态权限</description>
    <properties>
        <java.version>1.8</java.version>
        <mysql.version>8.0.28</mysql.version>
        <druid.version>1.2.6</druid.version>
        <mybatisPlus.version>3.5.1</mybatisPlus.version>
        <fastjson.version>1.2.78</fastjson.version>
        <jwt.version>0.9.1</jwt.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!-- DRUID -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatisPlus.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mybatisPlus.version}</version>
        </dependency>

        <!-- 模板引擎 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.28</version>
            <scope>compile</scope>
        </dependency>
        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- Token生成与解析-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jwt.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

代码生成

使用mybatisplus生成实体类,controller和service的java文件。

@SpringBootTest
class AutopermApplicationTests {

    @Autowired
    private DruidDataSource dataSource;

    @Test
    void contextLoads() {
        FastAutoGenerator.create(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword())
                .globalConfig(builder -> {
                    builder.author("liyongxuan") // 设置作者
                            .enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("D://"); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent("com.lyx") // 设置父包名
                            .moduleName("autoperm") // 设置父包模块名
                            .entity("entity")
                            .controller("controller")
                            .service("service")
                            .serviceImpl("impl")
                            .pathInfo(Collections.singletonMap(OutputFile.mapper, "E:\\WorkSpace\\autoperm\\src\\main\\java\\com\\lyx\\autoperm\\mapper"))
                            .pathInfo(Collections.singletonMap(OutputFile.entity, "E:\\WorkSpace\\autoperm\\src\\main\\java\\com\\lyx\\autoperm\\entity"))
                            .pathInfo(Collections.singletonMap(OutputFile.service, "E:\\WorkSpace\\autoperm\\src\\main\\java\\com\\lyx\\autoperm\\service"))
                            .pathInfo(Collections.singletonMap(OutputFile.serviceImpl, "E:\\WorkSpace\\autoperm\\src\\main\\java\\com\\lyx\\autoperm\\service\\impl"))
                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "E:\\WorkSpace\\autoperm\\src\\main\\resources\\mybatis")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("L_ROLE") // 设置需要生成的表名
                            .addInclude("L_USER")
                            .addInclude("L_PERMISSON")
                            .addInclude("L_USER_ROLE")
                            .addInclude("L_ROLE_PERMISSON")
                            .addTablePrefix("L_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }

}

生成实体类后将User类继承UserDetails,同时继承其方法。我们这里只是用enabled来判断状态,所以isAccountNonExpired、isAccountNonLocked、isCredentialsNonExpired三个方法直接返回true.

@TableName("L_USER")
public class User implements Serializable, UserDetails {

    private static final long serialVersionUID = 1L;

     /**
       * 用户编号
       */
    private String id;

     /**
       * 用户名
       */
    private String username;

     /**
       * 密码
       */
    private String password;

     /**
       * 真实姓名
       */
    private String name;

     /**
       * 年龄
       */
    private String age;

     /**
       * 性别
       */
    private String sex;

     /**
       * 手机号
       */
    private String phone;

     /**
       * 家庭住址
       */
    private String address;

     /**
       * 启用状态
       */
    private Integer enable;

     /**
       * 创建时间
       */
    private String createTime;

    @TableField(exist = false)
    private Set<String> permissions;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

     /**
       * 默认不适用该状态
       */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 默认不适用该状态
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 默认不适用该状态
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enable==1?true:false;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    public void setPermissions(Set<String> permissions) {
        this.permissions = permissions;
    }

    public Set<String> getPermissions() {
        return permissions;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
    public Integer getEnable() {
        return enable;
    }

    public void setEnable(Integer enable) {
        this.enable = enable;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getCreateTime() {
        return createTime;
    }

    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }
    
}

在UserServiceImpl中实现UserDetailsService,SpringSecurity就是调用UserDetailsServiceloadUserByUsername来查询用户信息以及权限列表的。

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
        // 校验用户名密码是否为空
        if(StringUtils.isEmpty(username)) throw new UserException(UserCodeEnum.USERNAME_IS_EMPTY);
        // 查询用户
        User userFromDB = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
        // 判断用户是否为空或账号不存在
        if(null == userFromDB){
            throw new UsernameNotFoundException("用户不存在");
        }
        // 查询登录用户的权限列表
        Set<String> permissions = permissionMapper.queryPermissions(userFromDB.getId());
        if(!CollectionUtils.isEmpty(permissions)){
            userFromDB.setPermissions(permissions);
        }

        return userFromDB;
    }

源码地址

https://github.com/Lyx0912/autopermission

Logo

前往低代码交流专区

更多推荐