Spring Security RBAC基于角色的权限管理

1,379 阅读3分钟

表设计

用户和角色多对多关系

角色和权限多对多关系

因为想要达到多对多的效果,所以需要单独维护两张关系表

几个查询操作

用户和角色

# 
select c.name ,role_code
from cat c , role  r , cat_role_relation cr
where c.id = cr.cat_id
and r.id = cr.role_id;

一个用户对应多个角色,反之每个角色也可以赋给不同的用户

角色和权限

#角色和权限多对多
select r.role_code ,p.api
from role r, permission  p ,role_permission_relation rp
where r.id = rp.role_id
and p.id = rp.permission_id;

一个角色对应了多个权限,反之这些权限也可以赋给不同的角色

根据猫猫名找到角色

#根据猫猫名获取RoleCode
select  r.role_code
from  role r left join cat_role_relation cr on r.id = cr.role_id
left join  cat c on  c.id = cr.cat_id
where c.name='maomao';

返回一个角色列表

获取权限

#根据角色列表返回所有权限
select p.api
from permission p left join role_permission_relation rp on p.id = rp.permission_id
left join role r on rp.role_id = r.id
where r.role_code IN('admin','user')

Mybatis

之前是用mybatis plus写的,各种条件查询器wrapper然后嵌套这种,这次改用mybatis直接写

获取角色列表

   @Select({"select  r.role_code",
            "from  role r left join cat_role_relation cr on r.id = cr.role_id",
            "left join  cat c on  c.id = cr.cat_id",
            "where c.name=#{name};"
    })
    List<String> getRoleCode(String name);

获取权限列表

  @Select({
            "<script>",
            "select p.api",
            "from permission p " ,
            "left join role_permission_relation rp on p.id = rp.permission_id",
            "left join role r on rp.role_id = r.id",
            "where r.role_code IN ",
            "<foreach collection='roleCodeList' item= 'roleCode'  open='(' separator =',' close=')'>",
             "#{roleCode}",
            "</foreach>",
            "</script>"
    })
    List<String> getPermission(@Param("roleCodeList") List<String> roleCodeList);

动态加载角色权限数据

UserDetails:校验需要的用户信息都在这里,详情看文档UserDetails的解释 => DOC

实现自己的UserDetails

继承这个接口,里面的几个布尔值暂时全部设为true

@Data
public class CatDetails implements UserDetails {

    private String name;
    private String password;
    private boolean isAccountNonExpired;
    private boolean isAccountNonLocked;
    private boolean isCredentialsNonExpired;
    private boolean isEnabled;
    Collection<? extends GrantedAuthority> authorities;

    //用户权限集合
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    //获取密码
    @Override
    public String getPassword() {
        return password;
    }

    //获取用户名
    @Override
    public String getUsername() {
        return name;
    }

    //账户是否没过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    //账户是否没被锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    //密码是否没过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    //账户是否可用 caotamade
    @Override
    public boolean isEnabled() {
        return  true;
    }
}

真正校验需要的Service,

@Component
public class CatDetailsService implements UserDetailsService {

    @Autowired
    private CatService catService;

    @Override
    public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
        //查询用户基本信息
        CatDetails catDetails = catService.getCatDetailsByName(name);

        //查询当前猫猫的角色列表
        List<String> roleCodeList = catService.getRoleCodeList(catDetails.getName());

        //根据当前猫猫的角色列表查询所拥有权限的api
        List<String> authorities = catService.getPermissionApi(roleCodeList);

        //添加标识前缀
        roleCodeList = roleCodeList.stream().map(roleCode -> "ROLE_" + roleCode).collect(Collectors.toList());
        authorities.addAll(roleCodeList); //角色也是权限,所以要添加到权限列表中

        catDetails.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(
                String.join(",", authorities)
        ));

        log.info(catDetails.toString());
        return catDetails;
    }
}

测试下CatDetilasService的返回结果,应该是这样的

CatDetails(name=maomao, 
           password=$2a$10$6YCw0UapkFqwiioxteD/TeXauzTYpYA361772L5FsQTnR50N4yvKC, 
           isAccountNonExpired=true, 
           isAccountNonLocked=true, 
           isCredentialsNonExpired=true,
           isEnabled=true, 
           authorities=
           [/cat/add, /cat/delete, /cat/update,
            /cat/search, /cat/search, ROLE_admin, ROLE_user])

\

指定Security的认证模式(FormLogin)然后编写规则

把CatDetailsService丢进去

动态鉴权

之前需要在config中写好访问URL时对应需要的权限,想想如果有100个路径难道要每个都手动指定权限吗

思路

动态鉴权的思路就是当我们访问时,拿到当前请求的uri 和 当前访问用户详情的权限列表,然后做对比,如果这个uri在认证主体的权限列表中,那么就返回true放行请求

  • 注 uri不是URL,是地址和端口号后面的路径,例如http://localhost:8099/cat/search中的uri就是

/cat/search

实现自己的鉴权逻辑

  1. 新建一个类叫它PermissionService
@Component("permissionService")
public class PermissionService {

    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        //拿到认证的主体
        Object principal = authentication.getPrincipal(); 
        //是否为UserDetails类型
        if (principal instanceof UserDetails) {
            //做一次强转
            UserDetails userDetails = (UserDetails) principal;
            
            //简单授权规则把uri填进去
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(request.getRequestURI());
            //判断是否含有该规则
            return userDetails.getAuthorities().contains(simpleGrantedAuthority);
        }
        return false;
    }
}

文档中描述它是获取主体

关于这个SimpleCrantedAuthority类型的权限,名这么长不用怕他,这玩意我们见过,文档中是这么描述的

在实现自己的UserDetails时那个权限集合就是存这个的

最后