(Spring Security)学习七:自定义用户资源权限动态控制

发布于:2023-01-02 ⋅ 阅读:(189) ⋅ 点赞:(0)

引言

上一篇文章实现了自定义用户动态登录,不同的用户必定有不同的用户权限和资源,我们需要根据不同的用户的角色以及资源,来给用于对应的权限控制。在查看本文章之前,需要实现了解RBAC权限设计思想和概念,以便更好的理解用户资源权限动态控制。

准备

定义好相关的数据表:用户表、角色表、资源表、用户角色关联表、角色资源关联表。此处以最基础的RBAC作为应用场景来实现。

实现

FilterSecurityInterceptor是SpringSecurity中最后一个Filter,作用非常重要,通过对FilterSecurityInterceptor安全拦截过滤接口的实现;组织资源角色关系的授权源(SecurityMetadataSource),过滤器将用户(User)的权限资源同授权源进行分析做出权限动态控制。
FilterSecurityInterceptor类的关系结构如下图:

在这里插入图片描述
SecurityMetadataSource
通过loadResourcesDefine方法构建权限资源对应的源数据,SecurityMetadataSource中getAttributes非常重要,通过getAttributes(Object o)方法返回对应的Object o的权限资源对应关系集合Collection<ConfigAttribute>,供AccessDecisionManager进行权鉴。

AccessDecisionManager
通过decide方法将Authentication用户的权限同Collection<ConfigAttribute>进行匹配来判断权限是否有效。

通过这两个核心做出权限动态控制。

代码示例

OursFilterSecurityInterceptor

public class OursFilterSecurityInterceptor extends FilterSecurityInterceptor {
    private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
    private FilterInvocationSecurityMetadataSource securityMetadataSource;
    private boolean observeOncePerRequest = true;

    public OursFilterSecurityInterceptor(){
        System.out.println("4:OursFilterSecurityInterceptor");
    }
    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("安全拦截器4.1");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        System.out.println("安全拦截器4.2");
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }

    public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    @Override
    public void destroy() {
        System.out.println("安全拦截器4.3");
    }

    @Override
    public Class<? extends Object> getSecureObjectClass() {
        System.out.println("安全拦截器4.4");
        return FilterInvocation.class;
    }


    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        System.out.println("安全拦截器4.5");
        if ((fi.getRequest() != null)
                && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
                && observeOncePerRequest) {
            // filter already applied to this request and user wants us to observe
            // once-per-request handling, so don't re-do security checking
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        }
        else {
            // first time this request being called, so perform security checking
            if (fi.getRequest() != null && observeOncePerRequest) {
                fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
            }

            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                super.finallyInvocation(token);
            }

            super.afterInvocation(token, null);
        }
    }


    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        System.out.println("安全拦截器4.6");
        return this.securityMetadataSource;
    }

    public void setSecurityMetadataSource(
            FilterInvocationSecurityMetadataSource newSource) {
        System.out.println("安全拦截器4.7");
        this.securityMetadataSource = newSource;
    }
}

OursSecurityMetadataSource

public class OursSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    /*public OursSecurityMetadataSource() {
        //System.out.println("2:OursSecurityMetadataSource");
    }*/

    @Autowired
    private IRoleService roleService;
    @Autowired
    private IAuthAccessService authAccessService;
    @Autowired
    private IMenuService menuService;

    private AntPathMatcher urlMatcher = new AntPathMatcher();
    //保存资源和权限的相应关系 key(资源url) value(权限)
    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;


    //此处需要根据 数据库结构调整进行改造,满足角色权限配置,同时满足超级管理员的实现
    //(超级管理员Code默认设定为ROLE_ADMIN)
    private void loadResourcesDefine() {
        System.out.println("安全元数据2.1:开始载入资源列表数据");
        resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
        List<AuthAccess> authAccesses = authAccessService.findAll();
        Collection<ConfigAttribute> configAttributes;
        for (AuthAccess authAccess : authAccesses) {
            //根据资源列表获取对于的role信息
            Role role = roleService.findById(authAccess.getRole_id());
            ConfigAttribute configAttribute = new SecurityConfig(role.getCode());
            //
            if (resourceMap.containsKey(authAccess.getRule_name())) {
                configAttributes = resourceMap.get(authAccess.getRule_name());
                configAttributes.add(configAttribute);
            } else {
                configAttributes = new ArrayList<ConfigAttribute>();
                configAttributes.add(configAttribute);
                resourceMap.put(authAccess.getRule_name(), configAttributes);
            }
        }
        //--加入超级权限对应菜单
        List<Menu> menus = menuService.findAll();
        ConfigAttribute configAttributeAdmin = new SecurityConfig("ROLE_ADMIN");
        for (Menu menu : menus) {
            if (resourceMap.containsKey(menu.getPermission())) {
                configAttributes = resourceMap.get(menu.getPermission());
                configAttributes.add(configAttributeAdmin);
            } else {
                configAttributes = new ArrayList<ConfigAttribute>();
                configAttributes.add(configAttributeAdmin);
                resourceMap.put(menu.getPermission(), configAttributes);
            }
        }

        //--展示权限对应关系--
        /*Set<String> set = resourceMap.keySet();
        Iterator<String> it = set.iterator();
        while (it.hasNext()) {
            String s = it.next();
            System.out.println("key:" + s + ",value:" + resourceMap.get(s));
        }*/
        //--
    }

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        loadResourcesDefine();
        //避免返回null值,否则AccessDecisionManager失效
        Collection<ConfigAttribute> collection = new LinkedList<>();
        //o 是一个URL?
        String url = ((FilterInvocation) o).getRequestUrl();
        System.out.println("请求地址为:" + url);
        //--
        Iterator<String> it = resourceMap.keySet().iterator();
        while (it.hasNext()) {
            String resUrl = it.next();
            //
            if (resUrl.indexOf("?") != -1) {
                resUrl = resUrl.substring(0, resUrl.indexOf("?"));
            }
            if (urlMatcher.match(resUrl, url)) {
                System.out.println("须要的权限是:" + resourceMap.get(resUrl));
                return resourceMap.get(resUrl);
            }
        }
        collection.add(new SecurityConfig("ROLE_ANONYMOUS"));
        return collection;
    }


    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        System.out.println("安全元数据2.3");
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        System.out.println("安全元数据2.4");
        return true;
    }
}

此处注意几点

    @Autowired
    private IRoleService roleService;
    @Autowired
    private IAuthAccessService authAccessService;
    @Autowired
    private IMenuService menuService;

该部分,是根据你自己的用户管理表接口来视情况去设计,不做赘述。

Collection<ConfigAttribute> getAttributes(Object o)

此处也往往是个坑,不要返回null值,当然你可以在OursAccessDecisionManager的decide中做判断。

OursAccessDecisionManager

public class OursAccessDecisionManager implements AccessDecisionManager {
    public OursAccessDecisionManager() {
        System.out.println("3:OursAccessDecisionManager");
    }

    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        //System.out.println("访问决策管理器3.1:验证用户是否具有一定的权限");
        if (collection == null) {
            return;
        }
        if (collection.size() <= 0) {
            return;
        }
        Iterator<ConfigAttribute> it = collection.iterator();
        String needResource = null;
        while (it.hasNext()) {
            ConfigAttribute authority = it.next();
            if (authority == null || (needResource = authority.getAttribute()) == null) {
                continue;
            }
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority ga : authorities) {
                if (needResource.equals(ga.getAuthority().trim())) {
                    return;
                }
            }
        }
        System.out.println("没有访问权限!");
        throw new AccessDeniedException("没有访问权限!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        //System.out.println("访问决策管理器2.2");
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        //System.out.println("访问决策管理器2.3");
        return true;
    }
}

配置

SecurityConfig

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AuthenticationSuccessHandler oursAuthenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler oursAuthenticationFailureHandler;
    @Autowired
    private LogoutSuccessHandler oursLogoutSuccessHandler;
    @Autowired
    private AuthenticationEntryPoint oursAuthenticationEntryPoint;
    @Autowired
    private UserDetailsService oursUserDetailsService;
    @Autowired
    private PasswordEncoder oursPasswordEncoder;
    @Autowired
    private OursSecurityMetadataSource oursSecurityMetadataSource;
    @Autowired
    private OursAccessDecisionManager oursAccessDecisionManager;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors().and()
                .csrf().disable()

                .authorizeRequests()
                .antMatchers("/login","/logout").permitAll()
                .anyRequest().authenticated()
                .and()

                .formLogin()
                .loginPage("/login")
                .permitAll()
                .defaultSuccessUrl("/index")
                //登录成功
                //.successHandler(oursAuthenticationSuccessHandler)
                //登录失败
                //.failureHandler(oursAuthenticationFailureHandler)
                .and()

                .logout()
                .logoutUrl("/logout")
                //注销成功
                //.logoutSuccessHandler(oursLogoutSuccessHandler)
                .permitAll()
                .and()

                //.addFilterBefore(oursFilterSecurityInterceptor(), FilterSecurityInterceptor.class)
                .headers().frameOptions().sameOrigin()
                .and()
                .exceptionHandling()
                //未认证处理
                //.authenticationEntryPoint(oursAuthenticationEntryPoint)
        ;
        http
                .addFilterAfter(oursFilterSecurityInterceptor(), ExceptionTranslationFilter.class);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers( "/css/**", "/images/**", "/js/**", "/plugins/**", "/fonts/**","/layui/**");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(oursUserDetailsService).passwordEncoder(oursPasswordEncoder);
    }


    private FilterSecurityInterceptor oursFilterSecurityInterceptor() throws Exception {
        OursFilterSecurityInterceptor filterSecurityInterceptor = new OursFilterSecurityInterceptor();
        filterSecurityInterceptor.setRejectPublicInvocations(false);
        filterSecurityInterceptor.setAccessDecisionManager(oursAccessDecisionManager);
        filterSecurityInterceptor.setAuthenticationManager(authenticationManager());
        filterSecurityInterceptor.setSecurityMetadataSource(oursSecurityMetadataSource);
        return filterSecurityInterceptor;
    }

}

至此,自定义用户资源权限动态控制实现了,感谢你的阅读,希望这个学习总结对你有所帮助,也感谢你的关注,我们共同畅游在这冷冷的语言世界吧。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到