SpringSecurity动态授权流程及实现

需求

如何通过修改数据库中某个角色的对某一访问路径的权限,对应的该角色用户就不能这URL访问了。

 

方式一

之前项目采用标注方式 UserDetails 中  authorities  直接存放的是权限字符,虽然也能动态的限制角色权限,但是做不到实时,只能登出后重新登录,才能生效。

1、Config启动标注

@EnableGlobalMethodSecurity(prePostEnabled = true)

2、Security认证,给UserDetails 中  authorities 中存入权限字符

详见SpringSecurity认证流程

3、在Controller方法上加上标注

@PreAuthorize("hasAuthority('sys:notesDevice:getData')")

数据设计采用RBAC经典5表设计

 

方式二

采用 修改过滤器链中  FilterSecurityInterceptor 中的数据源和表决器,可以做到实时

 

自做FilterSecurityInterceptor动态授权相关类图

 

我们首先来看,在Config中进行简单配置后采用的MetadataSource、Voter和表决器

.authorizeRequests()
.antMatchers("/test/user/**").hasRole("USER")
.antMatchers("/test/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()

 

一个请求不一定只有一个投票器,也可能有多个投票器,所以在投票器的基础上我们还需要表决机制。

默认投票器WebExpressionVoter,默认表决器AffirmativeBased,有一个投票器同意了,就通过。

 

 

主要流程

FilterSecurityInterceptor.doFilter 方法中建立 FilterInvocation(req,resp,chain)对象调用自身invoke方法,传入对象

invoke方法中,在 chain().doFilter 前有  super.beforeInvocation(fi),调用  AbstractSecurityInterceptor 的beforeInvocation方法

beforeInvocation方法中

Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
//根据请求地址,获得请求需要的角色

表决器进行表决

this.accessDecisionManager.decide(authenticated, object, attributes);
 /**
     *只要有一个投票器同意了,就通过
     *
     */
    public void decide(Authentication authentication, Object object,
                       Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
        int deny = 0;

        for (AccessDecisionVoter voter : getDecisionVoters()) {
            /**
             * 调用投票器去投票
             * 三个结果  通过、弃权、拒绝
             */
            int result = voter.vote(authentication, object, configAttributes);

            if (logger.isDebugEnabled()) {
                logger.debug("Voter: " + voter + ", returned: " + result);
            }

            switch (result) {
                case AccessDecisionVoter.ACCESS_GRANTED:
                    return;

                case AccessDecisionVoter.ACCESS_DENIED:
                    deny++;

                    break;

                default:
                    break;
            }
        }

        if (deny > 0) {
            throw new AccessDeniedException(messages.getMessage(
                    "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        }

        // To get this far, every AccessDecisionVoter abstained
        checkAllowIfAllAbstainDecisions();
    }

 

配置

前面可以看到

表决器 AffirmativeBased 对象创建好之后,还调用 postProcess 方法注册到 Spring 容器中去了

Config文件

    http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(customUrlDecisionManager);
                        object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                        return object;
                    }
                })
                .and()..........

自定义数据源

@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    //service注入
    @Autowired
    MenuService menuService;
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        //查询出菜单与角色关联表,及菜单,角色相关字段    注意排序
        List<Menu> menus = menuService.getAllMenusWithRole();
        for (Menu menu : menus) {
            if (antPathMatcher.match(menu.getUrl(), requestUrl)) {
                List<Role> roles = menu.getRoles();
                String[] str = new String[roles.size()];
                for (int i = 0; i < roles.size(); i++) {
                    str[i] = roles.get(i).getName();
                }
                //找到返回role集合
                return SecurityConfig.createList(str);
            }
        }
        //没有匹配返回
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

 

自定义表决器

@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        for (ConfigAttribute configAttribute : configAttributes) {
            String needRole = configAttribute.getAttribute();
            if ("ROLE_LOGIN".equals(needRole)) {
                if (authentication instanceof AnonymousAuthenticationToken) {
                    throw new InsufficientAuthenticationException("尚未登录,请登录!");
                }else {
                    //相应权限URL在库中不存在,但是已经登录,默认请求,只要登录即允许
                    return;
                }
            }
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    //匹配到了
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足,请联系管理员!");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值