首页 > 技术文章 > SpringSecurity整合SpringBoot中的动态权限配置

fkPrograming 2021-02-17 17:28 原文

SpringSecurity整合SpringBoot中的动态权限配置

数据库有三张表 menu(菜单表) 、 menu_role 和 role(角色表)

菜单表

url字段存放该菜单下的所有请求的一个通用形式,用于后面匹配请求是哪一个菜单

menu_role表连接menu表和role表

角色表

name为权限名

动态权限判定原理:

  1. 查询每一个资源的所有权限,拦截request,将requestUrl与资源url匹配

    将匹配到的资源对应的所有权限存入SpringSecurity管理

    未匹配到的资源,默认为登录即可访问的资源

  2. 制定决策,获取当前登录用户,将用户的权限集合与之前存储的访问资源

    的权限集合进行匹配,如果有交集就说明当前用户拥有权限访问该资源

实现过程:

查询所有资源及其权限

@Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private IMenuService menuService;

    AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * 将请求的url对应的角色加入到SecurityConfig中
     * @param o
     * @return
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        //获取请求的url
        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        //查询出来了每一个菜单及其对应所需要的角色
        List<Menu> menus = menuService.getMenusWithRole();
        //遍历资源,查找用户访问的资源
        for (Menu menu : menus) {
            //判断请求url与菜单角色是否匹配
            if(antPathMatcher.match(menu.getUrl(), requestUrl)){
                //将url匹配到的菜单所需要的所有角色加入到Security中
                String[] str = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
                //返回角色列表Collection<ConfigAttribute>
                return SecurityConfig.createList(str);
            }
        }
        //没有匹配的url默认登录即可访问
        return SecurityConfig.createList("ROLE_LOGIN");
    }

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

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}

制定决策

@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {

    /**
     * 该方法的参数Collection<ConfigAttribute>
     * 通过拦截Request传入FilterInvocationSecurityMetadataSource的getAttributes获取到的角色集合
     */
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {

        //遍历权限集合
        for (ConfigAttribute configAttribute : collection) {
            //当前url所需要的权限
            String needRole = configAttribute.getAttribute();
            //判断是否是登录即可访问的角色,此角色在CustomFilter中设置
            if ("ROLE_LOGIN".equalsIgnoreCase(needRole)) {
                //判断是否登录
                if (authentication instanceof AnonymousAuthenticationToken) {
                    throw new AccessDeniedException("尚未登录,请登录!");
                } else {
                    return;
                }
            }
            //判断用户权限是否为url所需权限
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if(authority.getAuthority().equals(needRole)){
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足,请联系管理员");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return false;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}

SecurityConfig配置

 				//动态权限配置
           http.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(customUrlDecisionManager);
                        object.setSecurityMetadataSource(customFilter);
                        return object;
                    }
                })

注意

  1. 用户登录时,不仅要查询用户基本信息,还需要查询拥有的权限信息

  2. 查询的所有权限需要封装成UserDetails指定的的权限集合

@Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //map就是一个加工方法,将stream中的每一个元素按照一定规则加工
        //collect就是能将stream转换为集合
        List<SimpleGrantedAuthority> authorities = roles
                .stream()
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());
        return authorities;
    }

推荐阅读