之前都是使用若依框架来实现的动态权限和菜单功能,但是一直想尝试自己来实现动态权限。所以这两天准备整合一下自己的所学知识,依据RBAC权限模型,使用SpringBoot+SpringSecurity+Vue来自己实现一下动态权限。
在数据库设计方面是根据RBAC权限模型来设计的,分别有user(用户)表,role(角色)表,permission(权限)表,user_role(用户角色)表和role_permission(角色权限)表。大致字段如下:
为了方便开发,选择了市面上主流的框架SpringBoot+Vue,安全框架使用SpringSecurity,ORM框架使用MyBatis-Plus。缓存使用Redis。
构建一个普通的SpringBoot项目,在pom文件导入以下依赖:
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.8version>
<relativePath/>
parent>
<groupId>com.lyx.autopermgroupId>
<artifactId>demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>autopermname>
<description>配合SpringSecurity动态权限description>
<properties>
<java.version>1.8java.version>
<mysql.version>8.0.28mysql.version>
<druid.version>1.2.6druid.version>
<mybatisPlus.version>3.5.1mybatisPlus.version>
<fastjson.version>1.2.78fastjson.version>
<jwt.version>0.9.1jwt.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>${druid.version}version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>${mybatisPlus.version}version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>${mybatisPlus.version}version>
dependency>
<dependency>
<groupId>org.apache.velocitygroupId>
<artifactId>velocity-engine-coreartifactId>
<version>2.0version>
dependency>
<dependency>
<groupId>org.freemarkergroupId>
<artifactId>freemarkerartifactId>
<version>2.3.28version>
<scope>compilescope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>${fastjson.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>${jwt.version}version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
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就是调用UserDetailsService的loadUserByUsername来查询用户信息以及权限列表的。
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