美文网首页SpringBoot配置
shiro(11)-shiroFilter(动态配置过滤器链)

shiro(11)-shiroFilter(动态配置过滤器链)

作者: 小胖学编程 | 来源:发表于2019-07-24 21:14 被阅读0次

    shiro安全控制目录

    shiro容器在启动的时候会默认加载一个ini文件,里面有核心的默认配置,当然也有对应的类,当然在实际运用中,我们可以通过查询数据库,将数据放入到InI对象中,动态的进行shiro的配置。

    1. 源码引来的思考

    一般我们配置shiro主过滤链是如下配置的,将String类型注入到filterChainDefinitions属性中。

    <!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <!--shiro的核心安全接口,这是属性是必须的-->
            <property name="securityManager" ref="securityManager"/>
            <!-- 要求登录时的链接,非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 -->
            <property name="loginUrl" value="/login/init"></property>
            <!-- 用户访问未对其授权的资源时,所显示的连接 -->
            <property name="unauthorizedUrl" value="/pages/error/403.jsp"/>
            <!--定义过滤器链-->
            <property name="filterChainDefinitions">
                <value>
                    <!--Shiro过滤器链的配置-->
                    /login/init/** = anon <!-- 对于登录相关不进行鉴权 -->
                    /login/login/** = anon <!-- 对于登录相关不进行鉴权 -->
                    /static/** = anon     <!-- 静态资源不进行鉴权 -->
                    /** = user
                </value>
            </property>
            <property name="filters">
                <map>
                    <entry key="user" value-ref="userFilter"></entry>
                </map>
            </property>
        </bean>
        <!--自定义拦截器-->
        <bean id="userFilter" class="com.springmvc.common.filter.SystemUserFilter"/>
    

    而在源码中,实际上是创建了Ini.section(实际上是一个Map<String,String>) [组件]对象,然后调用了 setFilterChainDefinitionMap(section);方法。那么我们可以自定义一个Ini.section对象(无需在XML中进行配置,可以动态配置过滤器链),然后set注入到filterChainDefinitionMap中呀。

    shiro的filterChainDefinitions属性源码配置:

        public void setFilterChainDefinitions(String definitions) {
            Ini ini = new Ini();
            ini.load(definitions);
           
            Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
            if (CollectionUtils.isEmpty(section)) {
                section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
            }
            //将设置的Ini.Section保存到FilterChainDefinitionMap中。
            setFilterChainDefinitionMap(section);
        }
    

    传入的definitions值:

                    /login/init/** = anon 
                    /login/login/** = user 
                    /static/** = anon     
                    /** = user
    

    2. 动态配置过滤器链

    Spring-FactoryBean学习(更灵活的创建bean的方式)

    Spring的XML配置文件将对象交由Spring管理的原理是:通过反射机制创建出对象,再交由Spring容器管理。那么我们在代码中若想将Bean注入到容器中,我们可以new出对象来。在调用对象的set方法或者构造方法完成属性注入。

    为什么动态配置过滤器链呢?

    一般shiro的权限控制原理就是

    1. 实现AuthorizingRealm类,实现里面的钩子方法doGetAuthenticationInfo() 认证回调方法,每次登陆时调用;doGetAuthorizationInfo()授权查询方法,当使用注解 @RequiresPermissions进行权限控制时回调,判断用户是否含有该权限。本质上就是AOP的体现。

    2. 但是通过给每个权限方法加注解,代码比较死板。所以若是可以将权限信息在项目启动的时候,配置在过滤器链中(自定义过滤器链可参考:shiro(8)-shiro过滤器+自定义过滤器)。方式就像授权拦截器一样,/user/**=perms["user:create"],那么就可以舍弃权限标签的用法。

    3. 关键点就是如何动态的获取user:create权限信息。并且默认的perms授权器要求满足所有的权限,才可放行。故我们需要自定义一个过滤器,满足任一权限,便可放行。

    1. 自定义授权拦截器myPermissionFilter:满足任一权限便可放行
    代码可参考:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

    2. 自定义授权拦截器myAuthcFilter:针对上送的请求,进行用户认证(可以在代码中使用getSubject(request, response).login(token);进行用户登录)

    如何动态配置过滤器链呢?

    1. 方式一:使用@Bean注解

        //读取配置文件中的(自定义的) ShiroFilter链配置
        @Value("${shiro.filterChainDefinitions}")
        private String filterChainDefinitions;
    
        @Bean
        public Ini.Section section(){
            //自定义过滤器链名称
            String restPermissionString = "myAuthcFilter,myPermissionFilter[{0}]";
            //加载默认的url过滤定义
            Ini ini = new Ini();
            //加载XML配置中的filterChainDefinitions配置
            ini.load(this.filterChainDefinitions);
            //配置Map<String,String>
            Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
            
            List<MyResource> resList =  MyResourceService.queryResourcList(query);
            // 将自定义url过滤添加到section中
            for (MyResourceres : resList) {
                if(StringUtils.isNotBlank(res.getResKey()) && StringUtils.isNotBlank(res.getResUrl())) {
                    if(!section.containsKey(res.getResKey())) {
                        section.put(res.getResUrl(), MessageFormat.format(restPermissionString, res.getResKey()));
                    }
                }
            }
            section.put("/**", "myAuthcFilter");
            return section;
        }
    

    2. 方式二:使用FactoryBean<Ini.Section>创建对象

    public class ChainDefinitionSectionMetaSource implements FactoryBean<Section> {
        @Autowired
        private SysRescService sysRescService;
        /**
         * 默认url过滤定义(shiro过滤器的filterChainDefinitions属性)
         */
        @Value("${shiro.filterChainDefinitions}")
        private String filterChainDefinitions;
    
        /**
         * 设置默认url过滤定义
         *
         * @param filterChainDefinitions
         */
        public void setFilterChainDefinitions(String filterChainDefinitions) {
            this.filterChainDefinitions = filterChainDefinitions;
        }
    
        @Override
        public Section getObject() throws Exception {
             //自定义过滤器链名称
            String restPermissionString = "myAuthcFilter,myPermissionFilter[{0}]";
            // 加载默认的url过滤定义
            Ini ini = new Ini();
            ini.load(this.filterChainDefinitions);
            Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
            
            List<MyResource> resList =  MyResourceService.queryResourcList(query);
            // 将自定义url过滤添加到section中
            for (MyResourceres : resList) {
                if(StringUtils.isNotBlank(res.getResKey()) && StringUtils.isNotBlank(res.getResUrl())) {
                    if(!section.containsKey(res.getResKey())) {
                        //   /order/query   myAuthcFilter,myPermissionFilter[sysuser_list]。/order/query路径访问的情况下,必须经过认证,并且有sysuser_list的权限
                        section.put(res.getResUrl(), MessageFormat.format(restPermissionString, res.getResKey()));
                    }
                }
            }
            section.put("/**", "myAuthcFilter");
            return section;
        }
        @Override
        public Class<?> getObjectType() {
            return Section.class;
        }
        @Override
        public boolean isSingleton() {
            return false;
        }
    }
    

    灵活的创建出复杂的bean对象后,通过XML将其交由Spring管理。(需要注意的是:即使class参数后面是FactoryBean<T>对象,但是这个Bean,却是InI.section类型。)

      <!--自定义ChainDefinitionSectionMetaSource-->
      <bean id="section" class="com.system.shiro.ChainDefinitionSectionMetaSource">
            <property name="filterChainDefinitions">
                <value>
                    <!-- Shiro 过滤链的定义 -->
                    /login/** = anon <!-- 对于登录相关不进行鉴权 -->
                    /register/** = anon <!-- 对于注册相关不进行鉴权 -->
                    /static/** = anon <!-- 静态资源不进行鉴权 -->
                </value>
            </property>
        </bean>
    
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <!-- Shiro的核心安全接口,这个属性是必须的 -->
            <property name="securityManager" ref="securityManager"/>
            <property name="unauthorizedUrl" value="/error/403"/>
            <!--由于filterChainDefinitionMap创建比较复杂,而且需要和数据库交互(灵活),故若是在配置文件中创建,那么繁杂而又死板。故采用FactoryBean在代码中创建Bean。-->
            <property name="filterChainDefinitionMap" ref="section" />
            <!--自定义拦截器-->
            <property name="filters">
                <map>
                    <entry key="myAuthcFilter" value-ref="myAuthcFilter"/>
                    <entry key="myPermissionFilter" value-ref="myPermissionFilter"/>
                </map>
            </property>
        </bean>
    

    文章参考

    apache shiro-动态创建filterChainDefinitions

    相关文章

      网友评论

        本文标题:shiro(11)-shiroFilter(动态配置过滤器链)

        本文链接:https://www.haomeiwen.com/subject/foyfrctx.html