Skip to content

Spring Security - 7 RBAC

本文主要介绍RBAC(Role-Based Access Controll, 基于角色的权限控制),是Spring Security系列第七篇文章,前六篇文章如下:

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

image-20250112160621956

image-20250112160703479

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. 测试效果

image-20250112161644447

image-20250112161728079