• Security——权限配置(数据库相关)


    简单配置

    先说一下简单的配置,使用Security已经实现的接口直接配置,库表设计如下:

    package cn.seaboot.admin.security.manager;
    
    import cn.seaboot.admin.security.bean.entity.SecurityChain;
    import cn.seaboot.admin.security.service.SecurityChainService;
    import cn.seaboot.common.core.CommonUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
    import org.springframework.security.core.session.SessionRegistry;
    import org.springframework.security.core.session.SessionRegistryImpl;
    import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
    import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
    
    import javax.annotation.Resource;
    import javax.sql.DataSource;
    import java.util.List;
    
    /**
     * Security configuration
     *
     * @author Mr.css
     * @date 2020-05-07 23:38
     */
    @Configuration
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
        private Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
    
        @Resource
        private SecurityChainService securityChainService;
    
        /**
         * HttpSecurity相关配置
         *
         * @param http HttpSecurity
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //鉴权配置,将数据库的权限配置加到系统中去
            ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
            List<SecurityChain> chains = securityChainService.queryList();
            if (CommonUtils.isNotEmpty(chains)) {
                for (SecurityChain chain : chains) {
                    if (!chain.getIsDisable() && !"anon".equals(chain.getExps())) {
                        if (chain.getMethod() == null) {
                            registry.antMatchers(chain.getUrl()).access(chain.getExps());
                        } else {
                            registry.antMatchers(HttpMethod.resolve(chain.getMethod()), chain.getUrl()).access(chain.getExps());
                        }
                        logger.info("[security] matcher: {} -> {}", chain.getUrl(), chain.getExps());
                    }
                }
            }
            //以下省略http其它配置
        }
    
        /**
         * 白名单配置,因为默认的投票决策方式,是1票通过的方式,只要满足1个条件,就算满足权限要求,因此白名单配置的所有Url,都是可以直接访问的
         *
         * @param web -
         */
        @Override
        public void configure(WebSecurity web) {
            WebSecurity.IgnoredRequestConfigurer ignoredRequestConfigurer = web.ignoring();
            List<SecurityChain> chains = securityChainService.queryList();
            if (CommonUtils.isNotEmpty(chains)) {
                for (SecurityChain chain : chains) {
                    if (!chain.getIsDisable() && "anon".equals(chain.getExps())) {
                        ignoredRequestConfigurer.antMatchers(chain.getUrl());
                        logger.info("[security] ignore: {}", chain.getUrl());
                    }
                }
            }
        }
    }

    复杂配置

    自定义权限匹配规则,这主要涉及到3个对象的使用。

    AccessDecisionManager:投票决策,与数据库种的权限配置进行比较,判断用户是不是有操作权限;

    FilterInvocationSecurityMetadataSource:元数据查找,相当于DAO,根据URL查找权限配置信息;

    ObjectPostProcessor:一个代码切面,帮你将代码植入特定鉴权环节的接口。

    具体配置如下:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.access.AccessDecisionManager;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
    
    /**
     * Security configuration
     *
     * @author Mr.css
     * @date 2020-05-07 23:38
     */
    @Configuration
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
        private Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
    
        /**
         * HttpSecurity相关配置
         *
         * @param http HttpSecurity
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            AuthorityPostProcessor urlPostProcessor = new AuthorityPostProcessor();
            urlPostProcessor.setAuthorityAccessDecisionManager(new AccessDecisionManager());
            urlPostProcessor.setAuthorityMetadataSource(new FilterInvocationSecurityMetadataSource());
    
            http.authorizeRequests()
                    .antMatchers("/login").permitAll()
                    .withObjectPostProcessor(urlPostProcessor)
                    .anyRequest().authenticated();
    
            //下面省略其它配置
        }
    }

    AccessDecisionManager

    系统默认实现有下面这么几个,Security是以投票的方式实现的,根据满足条件的数量,确定是否有权限进入系统(比如默认的,只要1个满足配置,就能直接进入系统)。

    如何切换默认的3种决策方式?咱也不知道,推荐查找AbstractSecurityInterceptor相关的代码。

    * {@link AffirmativeBased}   只需有一个投票赞成即可通过;
    * {@link ConsensusBased} 需要大多数投票赞成即可通过;
    * {@link UnanimousBased} 需要所有的投票赞成才能通过。
    import cn.seaboot.common.exception.ServiceException;
    import org.springframework.security.access.AccessDecisionManager;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.access.ConfigAttribute;
    import org.springframework.security.access.vote.AffirmativeBased;
    import org.springframework.security.access.vote.ConsensusBased;
    import org.springframework.security.access.vote.UnanimousBased;
    import org.springframework.security.authentication.InsufficientAuthenticationException;
    import org.springframework.security.core.Authentication;
    import org.springframework.stereotype.Component;
    
    import java.util.Collection;
    
    /**
     * 投票决策
     * <p>
     * 默认实现:
     * - {@link AffirmativeBased}   只需有一个投票赞成即可通过;
     * - {@link ConsensusBased}     需要大多数投票赞成即可通过;
     * - {@link UnanimousBased}     需要所有的投票赞成才能通过。
     *
     * @author Mr.css
     * @date 2022-01-06 19:32
     */
    @Component
    public class AuthorityAccessDecisionManager implements AccessDecisionManager {
        /**
         * 与角色对比,查看是否满足权限
         *
         * @param authentication   用户凭证
         * @param object           当前请求路径
         * @param configAttributes 权限集合
         * @throws AccessDeniedException -
         */
        @Override
        public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
            System.out.println("通过认证:" + authentication);
    //        throw new AccessDeniedException("access denied!");
        }
    
        /**
         * 因为我们的特殊写法,这些函数不应该被调用,直接抛出异常,方便我们跟踪意外调用的场景
         *
         * @param attribute -
         * @return -
         */
        @Override
        public boolean supports(ConfigAttribute attribute) {
            throw new ServiceException("UrlAccessDecisionManager's supports(ConfigAttribute attribute) method can not be used");
        }
    
        /**
         * 因为我们的特殊写法,这些函数不应该被调用,直接抛出异常,方便我们跟踪意外调用的场景
         *
         * @param clazz -
         * @return -
         */
        @Override
        public boolean supports(Class<?> clazz) {
            throw new ServiceException("UrlAccessDecisionManager's supports(Class<?> clazz) method can not be used");
        }
    }

    FilterInvocationSecurityMetadataSource

    import cn.seaboot.common.exception.ServiceException;
    import org.springframework.security.access.ConfigAttribute;
    import org.springframework.security.access.SecurityConfig;
    import org.springframework.security.web.FilterInvocation;
    import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
    import org.springframework.stereotype.Component;
    import org.springframework.util.AntPathMatcher;
    
    import java.util.Collection;
    
    /**
     * 权限数据源管理
     *
     * @author Mr.css
     * @date 2022-01-06 19:29
     */
    @Component
    public class AuthorityMetadataSource implements FilterInvocationSecurityMetadataSource {
    
        /**
         * 路径匹配工具
         */
        private AntPathMatcher antPathMatcher = new AntPathMatcher();
    
        /**
         * 根据Url查找匹配的路径列表
         *
         * @param object url
         * @return 所需的权限集合
         * @throws IllegalArgumentException -
         */
        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            System.out.println("获取URL所需的权限列表");
            String requestUrl = ((FilterInvocation) object).getRequestUrl();
            return SecurityConfig.createList("ROLE_LOGIN");
        }
    
        /**
         * 因为我们的特殊写法,这些函数不应该被调用,直接抛出异常,方便我们跟踪意外调用的场景
         *
         * @return -
         */
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            throw new ServiceException("SecurityMetadataSource's getAllConfigAttributes() method can not be used");
        }
    
        /**
         * 因为我们的特殊写法,这些函数不应该被调用,直接抛出异常,方便我们跟踪意外调用的场景
         *
         * @param clazz -
         * @return -
         */
        @Override
        public boolean supports(Class<?> clazz) {
            throw new ServiceException("SecurityMetadataSource's supports(Class<?> clazz) method can not be used");
        }
    }
    疯狂的妞妞 :每一天,做什么都好,不要什么都不做! 文中有不妥或者错误的地方还望指出,如果觉得本文对你有所帮助不妨【推荐】一下!如果你有更好的建议,可以给我留言讨论,共同进步!
  • 相关阅读:
    为什么需要Docker?
    一分钟学会《模板方法模式》
    2018再见|2019你好
    三分钟学会《门面模式》
    策略模式原来这么简单!
    外行人都能看得懂的机器学习,错过了血亏!
    我是如何将博客转成PDF的
    面试前必须知道的MySQL命令【explain】
    count(*)、count(1)和count(列名)的区别
    Linux shell去除字符串中所有空格
  • 原文地址:https://www.cnblogs.com/chenss15060100790/p/15774450.html
Copyright © 2020-2023  润新知