Spring Security实现基于RBAC的权限表达式动态访问控制的操作方法

基于RBAC的权限表达式动态访问控制是Spring Security中常用的一种权限控制方式。以下是具体的实现方法:

1. 定义RBAC模型

可参考以下示例:

### 角色
1. 管理员
2. 普通用户

### 权限
1. 用户管理:创建、删除用户
2. 文章管理:查看、修改、删除所有文章;创建、修改、删除自己的文章

### 资源
- 用户: /user/**
- 文章: /article/**

2. 实现自定义AccessDecisionManager

继承于AbstractAccessDecisionManager并实现decide()方法。其中,自定义的AccessDecisionManager需要获取到当前用户的所有角色和请求所需要的权限,然后判断用户所拥有的角色中是否有权限,决定用户是否可以访问特定的请求。

方法参数说明:

  • authentication:当前登录用户的身份验证对象(即spring security的Authentication对象)
  • object:封装了请求信息(如请求URL),可以通过它来判断用户请求的资源
  • configAttributes:可以获取到访问某个资源所需要的角色

可参考以下示例:

@Component
public class CustomAccessDecisionManager extends AbstractAccessDecisionManager {

    private final UserService userService;

    public CustomAccessDecisionManager(UserService userService,
                                       List<AccessDecisionVoter<?>> decisionVoters) {
        super(decisionVoters);
        this.userService = userService;
    }

    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        // 判断当前用户是否已登录
        if (authentication == null || !authentication.isAuthenticated()) {
            throw new AccessDeniedException("当前用户未登录");
        }

        // 获取当前用户所拥有的角色
        List<Role> userRoles = userService.getUserRoles(authentication.getName());
        if (userRoles == null || userRoles.isEmpty()) {
            throw new AccessDeniedException("当前用户未分配角色");
        }

        // 获取当前请求需要的角色
        Collection<String> requiredRoles = getRequiredRoles(configAttributes);
        if (requiredRoles == null || requiredRoles.isEmpty()) {
            throw new AccessDeniedException("当前请求未配置权限");
        }

        // 判断当前用户所拥有的角色和当前请求所需的角色是否匹配
        boolean isAuthorized = userRoles.stream()
                .map(Role::getRoleName)
                .anyMatch(requiredRoles::contains);
        if (!isAuthorized) {
            throw new AccessDeniedException("当前用户无权访问");
        }
    }

    private Collection<String> getRequiredRoles(Collection<ConfigAttribute> configAttributes) {
        if (configAttributes == null || configAttributes.isEmpty()) {
            return null;
        }
        return configAttributes.stream()
                .map(ConfigAttribute::getAttribute)
                .collect(Collectors.toList());
    }
}

3. 实现自定义Security MetadataSource

继承于FilterInvocationSecurityMetadataSource并实现getAttributes()supports()方法。其中,getAttributes()方法需要根据请求的URL返回该请求需要的角色,而supports()方法必须返回true。之后将该MetadataSource注入到Spring容器中。

可参考以下示例:

@Component
public class CustomInvocationSecurityMetadataSource extends AbstractSecurityInterceptor implements Filter {

    private final CustomAccessDecisionManager customAccessDecisionManager;

    public CustomInvocationSecurityMetadataSource(CustomAccessDecisionManager customAccessDecisionManager,
                                                   FilterInvocationSecurityMetadataSource metadataSource) {
        super();
        this.customAccessDecisionManager = customAccessDecisionManager;
        setAccessDecisionManager(customAccessDecisionManager);
        setSecurityMetadataSource(metadataSource);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        FilterInvocation filterInvocation = new FilterInvocation(request, response, chain);
        // 调用父类的beforeInvocation进行访问控制
        super.invoke(filterInvocation);
    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return super.obtainSecurityMetadataSource();
    }

    @Override
    public void destroy() {}

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

4. 配置Spring Security

对Spring Security进行自定义配置,将CustomAccessDecisionManager和CustomInvocationSecurityMetadataSource注册到Security配置中。

可参考以下示例:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final CustomAccessDecisionManager customAccessDecisionManager;
    private final FilterInvocationSecurityMetadataSource metadataSource;

    public SecurityConfig(CustomAccessDecisionManager customAccessDecisionManager,
                          FilterInvocationSecurityMetadataSource metadataSource) {
        this.customAccessDecisionManager = customAccessDecisionManager;
        this.metadataSource = metadataSource;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /**
         * 为了演示方便,这里省略了其他配置
         */
        http.authorizeRequests()
            .anyRequest()
            .access("@customInvocationSecurityMetadataSource.decide(request,authentication)");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**
         * 为了演示方便,这里省略了其他配置
         */
        auth.userDetailsService(userDetailsService());
    }

    @Bean
    public CustomAccessDecisionManager customAccessDecisionManager(UserService userService) {
        List<AccessDecisionVoter<?>> voters = Arrays.asList(
                new RoleVoter(),
                new AuthenticatedVoter(),
                new WebExpressionVoter()
        );
        return new CustomAccessDecisionManager(userService, voters);
    }

    @Bean
    public FilterInvocationSecurityMetadataSource customInvocationSecurityMetadataSource() throws Exception {
        UrlFilterInvocationSecurityMetadataSource metadataSource = new UrlFilterInvocationSecurityMetadataSource(
                securityInterceptor().getSecurityMetadataSource(), new PatternMatcher());
        // 用户 -> 角色 -> 权限
        metadataSource.addSecurityMapping(new SecurityMapping("/user/**", "ROLE_ADMIN"));
        metadataSource.addSecurityMapping(new SecurityMapping("/article/**", "ROLE_USER", "ROLE_ADMIN"));
        return metadataSource;
    }

    @Bean
    public CustomInvocationSecurityMetadataSource customInvocationSecurityMetadataSource(
            CustomAccessDecisionManager customAccessDecisionManager,
            FilterInvocationSecurityMetadataSource metadataSource
    ) {
        return new CustomInvocationSecurityMetadataSource(customAccessDecisionManager, metadataSource);
    }

    @Bean
    public SecurityInterceptor securityInterceptor() {
        return new SecurityInterceptor();
    }
}

示例说明

示例一:

当前用户为管理员,请求URL为/user/delete,则可以完成对当前用户的删除操作。若请求URL为/article/create则不能创建文章。

示例二:

当前用户为普通用户,请求URL为/user/create,则无法完成对其他用户创建的用户进行创建。但是可以通过请求URL/article/update来完成自己的文章修改。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Security实现基于RBAC的权限表达式动态访问控制的操作方法 - Python技术站

(0)
上一篇 2023年6月3日
下一篇 2023年6月3日

相关文章

  • 两种java实现二分查找的方式

    下面是详细讲解“两种java实现二分查找的方式”的攻略。 一、二分查找基本算法 二分查找算法的基本思想是:在一个有序数组中,查找一个元素,先找到数组的中间元素,然后将要查找的元素和中间元素进行比较,如果相等则直接返回中间元素,如果大于则在中间元素的右半部分继续查找,如果小于则在中间元素的左半部分继续查找,如此循环直到找到要查找的元素或者找不到为止。 Java…

    Java 2023年5月19日
    00
  • jsp中四种传递参数的方法

    下面我将详细讲解JSP中四种传递参数的方法: 1. URL传参 URL传参是一种最为简单和常见的传递参数方式,通过URL的?后面可以附上参数键值对。例如: http://localhost:8080/test.jsp?username=Tom&password=123456 在JSP中可以通过request.getParameter()方法获得对应的…

    Java 2023年6月15日
    00
  • JavaEE中用response向客户端输出中文数据乱码问题分析

    JavaEE中用Response向客户端输出中文数据时,由于编码方式的不同,可能会出现乱码问题。下面是解决该问题的完整攻略。 问题分析 出现中文乱码的原因是由于Java和浏览器显示中文时采用的编码方式不同。Java默认使用UTF-8编码,而浏览器则存在多种编码方式,如GB2312、GBK、UTF-8等。在Response输出响应的过程中,需要将Java编码方…

    Java 2023年5月20日
    00
  • Java中打乱一个数组的2种公平算法分享

    下面是“Java中打乱一个数组的2种公平算法分享”的完整攻略。 一、算法1:Fisher–Yates算法 1.算法原理 Fisher-Yates算法,又叫Knuth Shuffle算法,使用的是下标随机交换的方法,每次迭代时随机一个在当前位置及以后的位置(包括当前位置)之间的任意一个索引,然后将当前位置与该索引处的元素进行交换。该算法类似于每次从未处理的数据…

    Java 2023年5月19日
    00
  • JavaWeb中的简单分页完整代码(推荐)

    下面我来详细讲解JavaWeb中的简单分页完整代码攻略。 1. 原理简介 JavaWeb中的简单分页,主要通过对数据集进行分页处理。具体实现可以通过sql语句进行分页查询,也可以在页面上进行数据分页显示。 其中,采用sql语句进行分页查询的实现方式主要包含三个关键点: 分页参数计算 sql语句拼接 分页结果返回 2. 实现步骤 2.1 分页参数计算 分页参数…

    Java 2023年5月23日
    00
  • kafka手动调整分区副本数的操作步骤

    当需要手动调整Kafka集群中的某个主题的分区副本数时,可以通过添加或删除分区副本来实现。下面是手动调整分区副本数的操作步骤: 打开Kafka集群管理界面,例如Kafka Manager或Apache Kafka Web Console。 选择需要调整分区副本数的主题,点击进入主题管理页面。 打开分区列表,选择需要调整分区副本数的分区(例如第3个分区)。 点…

    Java 2023年5月20日
    00
  • Java的Struts框架报错“DuplicateSubscriptionException”的原因与解决办法

    当使用Java的Struts框架时,可能会遇到“DuplicateSubscriptionException”错误。这个错误通常由以下原因之一起: 重复的事件订阅:如果在多个位置订阅了同一个事件,则可能会出现此错误。在这种情况下,需要删除重复的事件订阅以解决此问题。 重复的事件处理程序:如果在多个位置定义了同一个事件处理程序,则可能会出现此错误。在这种情况下…

    Java 2023年5月5日
    00
  • springMVC几种页面跳转方式小结

    SpringMVC几种页面跳转方式小结 在SpringMVC中,有多种方式可以实现页面跳转。本文将介绍其中的几种方式,并提供示例说明。 方式一:使用redirect 使用redirect可以实现页面的重定向。在控制器方法中,我们可以使用”redirect:”前缀来指定重定向的URL。下面是一个示例的控制器方法: @GetMapping("/redi…

    Java 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部