spring security 自定义访问控制流程

在这里插入图片描述
先看上图
当认证成功之后我们往securityContex中放了Authentication对象,内部出了用户名密码还放了一个 Authorities,权限列表如下:我们基于rbac所以我们放了一个admin角色进去,实际上SimpleGrantedAuthority这个类约定只允许你放入角色,当然没有规定格式,你也可以放角色id 比如123;

		SimpleGrantedAuthority admin = new SimpleGrantedAuthority("admin");
        Set<SimpleGrantedAuthority> authorities=new HashSet<>();
        authorities.add(admin);
        userDetails.getAuthorities().addAll(authorities);

从这篇【过滤器访问控制流程分析】文章我们了解到
AbstractSecurityInterceptor.beforeInvocation方法中有这样两行代码
①表示从请求传进来的对象中提取访问当前请求链接需要的访问权限
②表示将1步骤获取的权限和当前认证用户的的信息进行比较判断是否能访问

① Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);
				//。。。省略this.accessDecisionManager.decide(authenticated, object, attributes);

接着我们来看第二个类图

在这里插入图片描述

先看看这个默认实现的DefaultFilterInvocationSecurityMetadataSource.getAttributes方法即上面①步骤调用到的

public Collection<ConfigAttribute> getAttributes(Object object) {
		final HttpServletRequest request = ((FilterInvocation) object).getRequest();
		for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
				.entrySet()) {
			if (entry.getKey().matches(request)) {
				return entry.getValue();
			}
		}
		return null;
	}

requestMap 是在子类被初始化的,但是子类又在这个ExpressionUrlAuthorizationConfigurer 类中被重写了最终是通过REGISTRY创建出来的

LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = REGISTRY
				.createRequestMap();

而REGISTRY 则是在ExpressionUrlAuthorizationConfigurer这个类中配置的

private final ExpressionInterceptUrlRegistry REGISTRY;

以上创建出来的内容就是通过以下几种方式在代码里面写死内容提取出来的。

http.authorizeRequests().antMatchers("/test/**").hasRole("admin")
http.authorizeRequests().mvcMatcher("/test/**").hasAuthority("xxx")
@Secured("hasRole('admin')")

很显然,默认的这个securityMetadataSource不能满足我们当前的需求。因为我们需要从数据库进行获取。所以我们需要自定义一个FilterInvocationSecutrityMetadataSource实现

public class UrlFilterInvocationSecurityMetadataSource implements
    FilterInvocationSecurityMetadataSource {
    private ResourcesProvider resourcesProvider;
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    public UrlFilterInvocationSecurityMetadataSource(ResourcesProvider resourcesProvider){
        this.resourcesProvider=resourcesProvider;
    }
    
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object)
        throws IllegalArgumentException {
        //获取请求地址
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        List<Menu> allMenu = resourcesProvider.getAllMenu();
        for (Menu menu : allMenu) {
            if (antPathMatcher.match(menu.getPath(), requestUrl)&&menu.getRoles().size()>0) {
                List<Role> roles = menu.getRoles();
                int size = roles.size();
                String[] values = new String[size];
                for (int i = 0; i < size; i++) {
                    values[i] = roles.get(i).getRoleName();
                }
                //匹配上则获取role返回
                return SecurityConfig.createList(values);
            }
        }
        //没有匹配上的资源,就创建一个匿名对象即可
        return SecurityConfig.createList("ROLE_ANONYMOUS");
    }
    //没用到放空即可
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }
    //这里也可以直接放回true
    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

接着我们在实现以上说的②步骤也可以自定义如何比较


public class UrlAccessDecisionManager implements AccessDecisionManager {
    
    @Override
    public void decide(Authentication authentication, Object object,
        Collection<ConfigAttribute> configAttributes)
        throws AccessDeniedException, InsufficientAuthenticationException {
        
        // TODO: 2020/11/16 这里写访问控制流程
        Iterator<ConfigAttribute> iterator = configAttributes.iterator();
        while (iterator.hasNext()) {
            ConfigAttribute ca = iterator.next();
            //当前请求需要的权限
            String needRole = ca.getAttribute();
            //当前用户所具有的权限
            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;
    }
}

扩展完成后如何生效呢?记得前面文章介绍过【ObjectPostProcessor解析
即可以在实例化的时候修改掉对象在http中配置即可

http.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                    o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);
                    o.setAccessDecisionManager(urlAccessDecisionManager);
                    return o;
                }
            })
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值