『Spring Security』(八) 动态权限控制

·  阅读 4345

这一次,我们接到的需求是,系统后台要能够配置用户的权限。

需求描述

在系统后台,给用户配置不同的权限,用户才可使用相应的功能。

解决方法

使用 RBAC 权限设计思想。

RBACRole-Based Access Control 的首字母,即基于角色的权限访问控制。目前这是用到最多的权限系统设计思想。

我们将系统的 url 路径都存到资源表中,然后用角色表多对多资源表。最后把角色赋予用户即可。

image-20201117151931194

根据我们之前对 Spring Security 工作流程的分析,我们应该实现两个接口。

FilterInvocationSecurityMetadataSource :通过此类,获取哪些角色可以访问该 url 。

AccessDecisionManager :通过此类,判断用户时候拥有上述中的角色。

实现

  1. MyAccessDecisionManager

    @Component
    @Slf4j
    public class MyAccessDecisionManager implements AccessDecisionManager {
     
        @Override
        public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
            // 获取用户拥有的权限信息
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            // 这里判断用户拥有的角色和该url需要的角色是否有匹配
            for (ConfigAttribute configAttribute : configAttributes) {
                String attribute = configAttribute.getAttribute();
                for (GrantedAuthority authority : authorities) {
                    if (attribute.equals(authority.getAuthority())) {
                        log.info("匹配成功.");
                        return;
                    }
                }
            }
            // 没有匹配就抛出异常
            throw new AccessDeniedException("权限不足,无法访问");
        }
        // 此 AccessDecisionManager 实现是否可以处理传递的 ConfigAttribute
        @Override
        public boolean supports(ConfigAttribute attribute) {
            return true;
        }
        // 此 AccessDecisionManager 实现是否能够提供该对象类型的访问控制决策。
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    }
    复制代码
  2. MySecurityMetadataSource

    @Component
    @Slf4j
    public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
     
        @Autowired
        ResourcesRepository resourcesRepository;
     
        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            // 获取url
            FilterInvocation filterInvocation = (FilterInvocation) object;
            String requestUrl = filterInvocation.getRequestUrl();
            // 获取拥有url的角色集合
            List<String> roles = resourcesRepository.getRolesByUrl(requestUrl);
            log.info("{} 对应的角色。{}",requestUrl,roles);
            if (CollectionUtils.isEmpty(roles)) {
                return null;
            }
            // 自定义角色信息 --> Security的权限格式
            String[] attributes = roles.toArray(new String[0]);
            return SecurityConfig.createList(attributes);
        }
     
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
     
        // 是否能为 Class 提供 Collection<ConfigAttribute>
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    }
    复制代码
  3. 修改 Security 配置

    @Override
        protected void configure(HttpSecurity http) throws Exception {
     
            // 关闭 session
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
            // 添加 jwt解析
            http.addFilterBefore(jwtTokenFilter,UsernamePasswordAuthenticationFilter.class);
            // 替换原有认证入口filter
            http.addFilterAt(myAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
     
            // 动态权限控制
            http.authorizeRequests()
                    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                        @Override
                        public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                            o.setSecurityMetadataSource(mySecurityMetadataSource);
                            o.setAccessDecisionManager(myAccessDecisionManager);
                            return o;
                        }
                    })
                    .anyRequest()
                    .authenticated()
                    .and()
                    .csrf()
                    .disable();
     
            // 认证异常
            http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
                response.setContentType("application/json;charset=utf-8");
                PrintWriter out = response.getWriter();
                HashMap<String, Object> result = new HashMap<>();
                result.put("code","111112");
                result.put("msg",authException.getMessage());
                out.write(new ObjectMapper().writeValueAsString(result));
                out.flush();
                out.close();
            });
            // 鉴权异常
            http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
                response.setContentType("application/json;charset=utf-8");
                PrintWriter out = response.getWriter();
                HashMap<String, Object> result = new HashMap<>();
                result.put("code","111113");
                result.put("msg",accessDeniedException.getMessage());
                out.write(new ObjectMapper().writeValueAsString(result));
                out.flush();
                out.close();
            });
        }
    复制代码

登陆

已预配置两个用户,其中一个为admin,另外一个为user。

admin 用户可以访问两个接口,user 用户只可以访问 user 接口。

@RestController
@RequestMapping
public class SecurityController {
 
    @GetMapping("/admin")
    public String admin() {
        return "hello,admin";
    }
 
    @GetMapping("/user")
    public String user() {
        return "hello,user";
    }
}
复制代码
  1. admin 用户访问 admin接口

    image-20201117155405583

  2. admin 用户访问 user接口

    image-20201117155435974
  3. user 用户访问 admin接口

    image-20201117155621659

  4. user 用户访问 user接口

    image-20201117155706343

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改