欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

史上最简单的Spring Security教程(十五):资源权限动态控制(FilterSecurityInterceptor)

程序员文章站 2022-06-02 23:24:51
...

 

在前面的讲解中,我们分别对动态用户、动态权限的实现做了相关介绍。可能大家在看的过程中,会发现一个问题:目前都是通过注解控制权限的,并且角色是事先定义好的,且需要数据库、Java程序保持一致。

这是非常不友好的,不能*的定义角色及其所能控制的资源。角色比较少且固定的业务场景还好,如OA,只有管理员和普通用户两种角色。但是遇到大型业务系统,角色细且繁多,需要自定义,且需要频繁变更其所拥有的资源,那么,目前的形式就显得非常笨拙。

接下来,就如何进行资源权限动态控制进行讲解,要实现的目标为:Java程序、数据库无需商定角色标识,以及角色所拥有的资源,一切均通过功能动态维护。

下面,就开始吧。

首先,定义数据结构。

create table SYS_FUNC
(
    ID                   varchar(32) not null comment '主键',
    NAME                 varchar(60) comment '功能名称',
    URL                  varchar(60) comment '功能地址',
    PID                  varchar(32) comment '父功能id',
    SORT                 int comment '顺序号',
    GMT_CREATE           timestamp default CURRENT_TIMESTAMP comment '新增时间,默认当前时间,不随数据改变而改变',
    GMT_MODIFIED         timestamp default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment '修改时间,默认当前时间,随数据改变而改变',
    primary key (ID)
);
​
alter table SYS_FUNC comment '系统功能';
create table SYS_ROLE_FUNC
(
    ID                   varchar(32) not null comment '主键',
    ROLE_ID              varchar(32) comment '角色ID',
    FUNC_ID              varchar(32) comment '功能ID',
    primary key (ID)
);
​
alter table SYS_ROLE_FUNC comment '角色功能';

 

然后,定义功能Dao,查询角色与功能映射关系。

public List<SysFuncRole> listFuncRole() {
    String sql = "select sys_role.id roleId,\n" +
        "       sys_role.code roleCode,\n" +
        "       sys_func.id funcId,\n" +
        "       sys_func.url url\n" +
        "  from sys_func, sys_role_func, sys_role\n" +
        " where sys_func.id = sys_role_func.func_id\n" +
        "   and sys_role_func.role_id = sys_role.id\n";
​
    return list(sql, new HashMap<>(), new BeanPropertyRowMapper<>(SysFuncRole.class));
}

 

自定义 FilterSecurityInterceptor。注意,该类没有任何实质性内容,空类,但不可省略

public class CustomFilterSecurityInterceptor extends FilterSecurityInterceptor {
}

 

组织 FilterSecurityInterceptor 所需要的 SecurityMetadataSource

private LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> obtainRequestMap() {
    List<SysFuncRole> sysFuncRoles = this.funcDao.listFuncRole();
​
    if (CollectionUtils.isEmpty(sysFuncRoles)) {
        return new LinkedHashMap<>();
    }
​
    Map<String, Set<String>> urlRoleMap = new HashMap<>();
​
    for (SysFuncRole sysFuncRole : sysFuncRoles) {
        String url = determineAntUrl(sysFuncRole.getUrl());
​
        Set<String> configAttributes = urlRoleMap.get(url);
​
        if (configAttributes == null) {
            configAttributes = new HashSet<>();
        }
​
        configAttributes.add(sysFuncRole.getRoleCode());
        urlRoleMap.put(url, configAttributes);
    }
​
    LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
​
    for(String url : urlRoleMap.keySet()) {
        Set<String> needRoles = urlRoleMap.get(url);
​
        // 注意此处,我们设置ConfigAttribute为 ROLE_ 前缀加上角色标识,与 CustomJdbcUserDetailsService 里面组织UserDetails设置角色标识呼应
        requestMap.put(new AntPathRequestMatcher(url), needRoles.stream().map(role -> new SecurityConfig("ROLE_" + role)).collect(Collectors.toSet()));
    }
​
    return requestMap;
}

 

需要注意,在本例中,我们假设一旦给用户分配了某个功能,即代表该功能下的所有操作用户都可以访问。另外,如果一个功能有多个角色控制,那么我们默认只要分配了其中的任何一个角色,都可以访问该功能。如果有别的业务场景,可参考详细源码适当改造,如满足所有角色才能访问功能(更换访问控制管理器中的投票器实现)、细分功能权限控制(修改SecurityMetadataSource组织逻辑等等。

配置自定义的 FilterSecurityInterceptor。

private FilterSecurityInterceptor customFilterSecurityInterceptor() throws Exception {
    CustomFilterSecurityInterceptor filterSecurityInterceptor = new CustomFilterSecurityInterceptor();
    filterSecurityInterceptor.setSecurityMetadataSource(new DefaultFilterInvocationSecurityMetadataSource(obtainRequestMap()));
    filterSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
    filterSecurityInterceptor.setAuthenticationManager(authenticationManager());
    return filterSecurityInterceptor;
}

 

最后,把自定义的 FilterSecurityInterceptor 配置到 Spring Security 中。

http.addFilterAfter(customFilterSecurityInterceptor(), FilterSecurityInterceptor.class);

另外,需要把 Controller 中所有 @PreAuthorize 注解 和 Spring Security 配置类中的 @EnableGlobalMethodSecurity 注解删除。

好了,一切已经准备就绪。启动系统,访问个人中心,如预想的一样,可以正常访问。

史上最简单的Spring Security教程(十五):资源权限动态控制(FilterSecurityInterceptor)

接下来,我们把角色用户映射表中当前用户的的数据先备份以下,然后删除。再来访问一下个人中心,已经不能访问,提示403无权限。

史上最简单的Spring Security教程(十五):资源权限动态控制(FilterSecurityInterceptor)

然后,我们再执行备份的sql,把映射关系补回来,再访问个人中心,熟悉的界面又回来了。

资源权限动态控制实现完成。

其它详细源码,请参考文末源码链接,可自行下载后阅读。

 

源码

 

github

 

https://github.com/liuminglei/SpringSecurityLearning/tree/master/15

 

gitee

 

https://gitee.com/xbd521/SpringSecurityLearning/tree/master/15

 

 

 

 

史上最简单的Spring Security教程(十五):资源权限动态控制(FilterSecurityInterceptor)

回复以下关键字,获取更多资源

 

SpringCloud进阶之路 | Java 基础 | 微服务 | JAVA WEB | JAVA 进阶 | JAVA 面试 | MK 精讲

史上最简单的Spring Security教程(十五):资源权限动态控制(FilterSecurityInterceptor)

 

 

 

笔者开通了个人微信公众号【银河架构师】,分享工作、生活过程中的心得体会,填坑指南,技术感悟等内容,会比博客提前更新,欢迎订阅。

史上最简单的Spring Security教程(十五):资源权限动态控制(FilterSecurityInterceptor)