Appearance
Spring Security - 7 RBAC
本文主要介绍RBAC(Role-Based Access Controll, 基于角色的权限控制),是Spring Security系列第七篇文章,前六篇文章如下:
- Spring Security - 1 初认识
- Spring Security - 2 身份验证(Authentication)
- Spring Security - 3 身份验证之数据库
- Spring Security - 4 JWT
- Spring Security - 5 使用JWT登录
- Spring Security - 6 权限校验介绍
1. RBAC模型介绍
RBAC全称Role-Based Access Controll ,是基于角色的权限控制模型,用于管理用户权限。其中分为三个实体:
- 权限(Permission):表示可以做某件事的权限;
- 角色(Role):一系列权限的合集;
- 用户(User):表示实际登录用户,其中某个用户可以有多个角色;
上面的ER图表示,角色可以拥有多个权限,权限也可以被多个角色所拥有;用户可以有多个角色,角色也可以被多个用户所拥有。
2. SQL语句
基于上面的ER图,我们可以创建五张表用来表示上面的关系:
- user表:表示用户实体;
- role表:表示角色实体;
- permission表:表示权限实体;
- user_role表:表示用户与角色的关系;
- role_permission表:表示角色与权限的关系;
以PostgreSQL数据库为例,SQL语句如下:
sql
CREATE TABLE "user" ( "id" SERIAL PRIMARY KEY, "username" VARCHAR NOT NULL UNIQUE, "password" VARCHAR NOT NULL );
CREATE TABLE "role" ( "id" SERIAL PRIMARY KEY, "name" VARCHAR NOT NULL, "descption" TEXT );
CREATE TABLE "permission" ( "id" SERIAL PRIMARY KEY, "name" VARCHAR NOT NULL, "description" TEXT );
CREATE TABLE "user_role" ( "user_id" INT NOT NULL, "role_id" INT NOT NULL, PRIMARY KEY ( "user_id", "role_id" ) );
CREATE TABLE "role_permission" ( "role_id" INT NOT NULL, "permission_id" INT NOT NULL, PRIMARY KEY ( "role_id", "permission_id" ) );并适当插入一些测试数据:
sql
SELECT
u.*,
pe.NAME,
pe.description
FROM
"user" u
LEFT JOIN "user_role" ur ON u.ID = ur.user_id
LEFT JOIN "role_permission" rp ON ur.role_id = rp.role_id
LEFT JOIN "permission" pe ON rp.permission_id = pe.ID
WHERE
u.ID =8

3. 在Java中查询带权限的用户信息
3.1 实体
首先准备三个实体对应数据库中的表:
java
@Data
public class Permission {
private int id;
private String name;
private String description;
}java
@Data
public class Role {
private int id;
private String name;
private String description;
private List<Permission> permissionList;
}java
@Data
public class User {
private int id;
private String username;
private String password;
private List<Role> roleList;
}3.2 Mapper接口及SQL语句
然后准备Mapper接口以及查询语句:
java
@Mapper
public interface RoleMapper {
List<Role> selectRolesByUserId(int userId);
List<Permission> selectPermissionsByRoleId(int roleId);
}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.example.springsecuritydemo.mapper.RoleMapper">
<resultMap id="roleMap" type="org.example.springsecuritydemo.model.Role">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="description" property="description"/>
<collection property="permissionList"
select="org.example.springsecuritydemo.mapper.RoleMapper.selectPermissionsByRoleId"
column="id"/>
</resultMap>
<select id="selectRolesByUserId" resultMap="roleMap">
select r.* from "user_role" ur
left join "role" r on r.id=ur.role_id
where ur.user_id=#{userId}
</select>
<select id="selectPermissionsByRoleId" resultType="org.example.springsecuritydemo.model.Permission">
select p.* from "role_permission" rp
left join "permission" p on p.id=rp.permission_id
where rp.role_id=#{roleId}
</select>
</mapper>java
@Mapper
public interface UserMapper {
User getUserByUsername(String username);
void insertUser(User user);
}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.example.springsecuritydemo.mapper.UserMapper">
<insert id="insertUser">
insert into "user" (username,password) values (#{username},#{password})
</insert>
<resultMap id="userResultMap" type="org.example.springsecuritydemo.model.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<collection
property="roleList"
select="org.example.springsecuritydemo.mapper.RoleMapper.selectRolesByUserId"
column="{userId=id}">
</collection>
</resultMap>
<select id="getUserByUsername" resultMap="userResultMap">
select * from "user" where username = #{username}
</select>
</mapper>4. 获取权限信息并写入UserDetails实体中
在之前的测试程序中,我们是写死的权限信息。现在我们可以将数据库中查询出来的权限信息写入UserDetails中了:
java
@Service
public class DBUserDetailsService implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.getUserByUsername(username);
if(user == null){
throw new UsernameNotFoundException("User not found");
}
List<String> authorityList = user.getRoleList()
.stream()
.flatMap(x -> x.getPermissionList()
.stream()
.map(y -> y.getName().toUpperCase()
)
)
.collect(Collectors.toList());
return new UserPricipal(user, authorityList);
}
}5. Controller
在Controller中的接口方法上指定权限:
java
@RestController
public class HelloController {
@GetMapping("/hello")
@PreAuthorize("hasAuthority('HELLO')")
public String hello(){
return "hello";
}
@GetMapping("/num")
@PreAuthorize("hasAuthority('NUM')")
public int num(){
return new Random().nextInt();
}
@GetMapping("/greet")
@PreAuthorize("hasAuthority('GREET')")
public String greet(@RequestParam String name){
return "hello "+name;
}
}6. 测试效果

