本博客旨在记录个人的一个RBAC学习过程,因为刚学SpringBoot,RBAC也涉及到数据库的知识,本文亦可作为SpringBoot及数据库的一个入门参考。
用过linux系统的应该都知道在linux系统中每个文件都有着r可读w可写x可执行三种权限,不同的用户对同一个文件也有着不同的权限,这和RBAC是类似的,RBAC全称Role-based access control(基于用户的权限控制),为了描述RBAC,本文将建立如下5张表,
user、role、permission、user_role、role_permission
从图可以看出有5个实体类,用户,角色,权限,用户角色,角色权限,最终我们要达到的目的是输入一个用户,输出此用户的所有权限。看到这有些人可能会想为什么不直接建立一个用户-权限表,在用户少的时候当然是可以的,但若有成千上万个用户,这个表建立及修改就比较麻烦了,但加上一个角色后,耦合性就大大降低,建立修改方便多了。
为了进行测试,在每个表中插入一些数据。
user | ||
id | username | username_zh |
---|---|---|
1 | user1 | 用户1 |
2 | user2 | 用户2 |
3 | user3 | 用户3 |
role | ||
id | rolename | rolename_zh |
---|---|---|
1 | admin | 管理员 |
2 | assistant | 助理 |
3 | consumer | 普通用户 |
permission | ||
id | permissionname | permissionname_zh |
---|---|---|
1 | read | 读 |
2 | write | 写 |
3 | delete | 删除 |
user_role | ||
id | uid | rid |
---|---|---|
1 | 1 | 1 |
2 | 2 | 2 |
3 | 3 | 3 |
role_permission | ||
id | rid | pid |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
3 | 1 | 3 |
4 | 2 | 1 |
5 | 2 | 2 |
6 | 3 | 1 |
这样一个简单的SpringBoot工程就建立好了。
接着我们在数据库中建立上文提过的5张表。
本博客中我们将采用Spring-data-jpa实现mysql数据库的读写,为此在maven库中添加以下两个依赖
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
8.0.17
接着在SpringBoot工程的java子路径下建立如下四个目录
这几个目录对应着SpringMvc模型,Controller为控制层,dao为持久层,services为业务逻辑层,pojo为持久层的实体类,下面开始编写具体类。
为了方便,本文将使用全注解的方式进行配置,所以在编写具体类之前,首先了解一下相关注解。
Spring框架相关注解
@Autowired
@Autowired顾名思义,就是自动装配,其作用是为了消除代码Java代码里面的getter/setter与bean属性中的property。当然,getter看个人需求,如果私有属性需要对外提供的话,应当予以保留。
@Autowired默认按类型匹配的方式,在容器查找匹配的Bean,当有且仅有一个匹配的Bean时,Spring将其注入@Autowired标注的变量中。
@Qualifier
如果容器中有一个以上匹配的Bean,则可以通过@Qualifier注解限定Bean的名称
@Resource
@Resource注解与@Autowired注解作用非常相似
这是详细一些的用法,说一下@Resource的装配顺序:
(1)、@Resource后面没有任何内容,默认通过name属性去匹配bean,找不到再按type去匹配
(2)、指定了name或者type则根据指定的类型去匹配bean
(3)、指定了name和type则根据指定的name和type去匹配bean,任何一个不匹配都将报错
然后,区分一下@Autowired和@Resource两个注解的区别:
(1)、@Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配
(2)、@Autowired是Spring的注解,@Resource是J2EE的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了
Spring属于第三方的,J2EE是Java自己的东西,因此,建议使用@Resource注解,以减少代码和Spring之间的耦合。
@Service
@Service注解,其实做了两件事情:
(1)、声明此类是一个业务层Bean,其他的类可以使用@Autowired自动入。
(2)、bean中的id是类名且首字母小写。
若想改bean的名字可以在@service里加参数@Service(“name”)
@Scope
@Scope(“singleton”)表示bean为单例@Scope("prototype ")表示bean原型即每次都会new一个新的出来
@Controller
@Controller用于标注控制层组件,也就是Action
@ Repository
@Repository对应数据访问层Bean
@Component
@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注,此注解不建议使用。
Spring data jpa相关注解
@Entity
@Entity表明该类是一个实体类 它默认对应数据库中的user(class字段都转为小写,驼峰命名转换为_连接)
@Table
@Table 当实体类与其数据库不同名时需要使用@Table注解说明,该标注与@Entity 注解并列使用。@Table注解的常用选项是 name,用于指明数据库的表名
@JsonIgnoreProperties
@JsonIgnoreProperties 标记不生成json对象的属性,由于jpa底层是hibernate,hibernate会给被管理的pojo加入一个hibernateLazyInitializer属性,jsonplugin会把hibernateLazyInitializer也拿出来操作,并读取里面一个不能被反射操作的属性会产生异常,所以在生成json对象时要忽略这个属性
@Id
@Id 用于声明一个实体类的属性映射为数据库的主键列
@GeneratedValue
@GeneratedValue用于标注主键的生成策略,通过strategy属性指定。默认情况下,JPA自动选择一个最合适底层数据库的主键生成策略:SqlServer对应identity,MySql对饮auto increment。
在javax.persistence.GenerationType中定义了以下几种可供选择的策略:
— IDENTITY:采用数据库ID自增长的方式来自增主键字段,Oracle不支持这种方式(oracle12g后,应该支持了。);
— AUTO:JPA自动选择合适的策略,是默认选项;
— SEQUENCE:通过序列产生主键,通过@SequenceGenerator注解指定序列名,MySql不支持这种方式。
— TABLE:通过表产生键,框架借助由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。
@Column
当实体的属性与其映射的数据库表的列不同名时需要使用@Column标注说明,该属性通常置于实体的属性声明语句之前,还可与@Id标注一起使用。
@Column标注的常量属性是name,用于设置映射数据库表的列名。
@Transient
表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性
@Temporal
在核心的JAVA API中并没有定义Date类型的精度(temporal precision)。而在数据库中,表示Date类型的数据类型有DATE,TIME和TEIMSTAMP三种精度(即单纯的日期,时间,或者两者兼备)。在运行属性映射是可使用@Temporal注解来调整Date精度。
下面开始创建相关类,首先是pojo实体类
@Entity
@Table(name = "user")
@JsonIgnoreProperties({"handler","hibernateLazyInitializer"})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
int id;
String username;
@Column(name = "username_zh")
String usernameZh;
@Entity
@Table(name = "role")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
int id;
String rolename;
@Column(name = "rolename_zh")
String rolenameZh;
@Entity
@Table(name = "permission")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
int id;
String permissionname;
@Column(name = "permissionname_zh")
String permissionnameZh;
@Entity
@Table(name = "user_role")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class UserRole {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
int id;
int uid;
int rid;
@Entity
@Table(name = "role_permission")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class RolePermission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
int id;
int rid;
int pid;
Dao层类
public interface UserDAO extends JpaRepository<User,Integer> {
User findByUsername(String username);
}
public interface RoleDAO extends JpaRepository<Role, Integer> {
Role findById(int id);
}
public interface PermissionDAO extends JpaRepository<Permission, Integer> {
Permission findById(int id);
}
public interface UserRoleDAO extends JpaRepository<UserRole,Integer> {
List<UserRole> findAllByUid(int uid);
}
public interface RolePermissionDAO extends JpaRepository<RolePermission,Integer> {
List<RolePermission> findAllByRid(int rid);
}
service层类
@Service
public class UserService {
@Autowired
UserDAO userDAO;
public User findByUserName(String username) {
return userDAO.findByUsername(username);
}
}
@Service
public class RoleService {
@Autowired
RoleDAO roleDAO;
public Role findRoleById(int rid){
return roleDAO.findById(rid);
}
}
@Service
public class PermissionService {
@Autowired
PermissionDAO permissionDAO;
public Permission findPermissionById(int mid){
return permissionDAO.findById(mid);
}
}
@Service
public class UserRoleService {
@Autowired
UserRoleDAO userRoleDAO;
public List<UserRole> findRolesByUser(User user){
int uid = user.getId();
return userRoleDAO.findAllByUid(uid);
}
}
@Service
public class RolePermissionService {
@Autowired
RolePermissionDAO rolePermissionDAO;
public List<RolePermission> findAllByRole(Role role){
return rolePermissionDAO.findAllByRid(role.getId());
}
}
控制层类
@RestController
public class MenuController {
@Autowired
UserService userService;
@Autowired
UserRoleService userRoleService;
@Autowired
RolePermissionService rolePermissionService;
@Autowired
RoleService roleService;
@Autowired
PermissionService permissionService;
@GetMapping("/api/permission")
public List<Permission> permission(String username) {
User user = userService.findByUserName(username);
List<UserRole> userRoles = userRoleService.findRolesByUser(user);
List<Permission> permissions = new ArrayList<>();
for(UserRole userRole : userRoles){
Role role = roleService.findRoleById(userRole.getRid());
List<RolePermission> rolePermissions = rolePermissionService.findAllByRole(role);
for(RolePermission rolePermission:rolePermissions){
Permission permission = permissionService.findPermissionById(rolePermission.getPid());
permissions.add(permission);
}
}
return permissions;
}
}
这边可以看到并没有用@controller注解,而是使用了@RestController注解
先讲讲@RestController的产生,从Spring 4.0以后产生的,用来将json/xml数据发送到前台页面,而不是返回视图页面。@RestController和@Controller的区别@RestController加在类上面的注解,使得类里面的每个方法都将json/xml返回数据加返回到前台页面中。
在Spring4.3版本以后,提供了@GetMapping注解更方便了开发
@GetMapping等价于@RequestMapping的GET请求方式
最后我们采用properties文件格式配置本地数据库的连接,及本项目的访问端口号
server.port=8443
spring.datasource.url=jdbc:mysql://localhost:3306/rdbc_study?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=123456(改成自己的密码)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto = none
下面我们将使用Postman进行测试
结果与预期的一致~~~
本文demo下载地址https://download.csdn.net/download/sdnyqfyqf/12328534
点关注不迷路~~