Shiro权限管理框架(三):Shiro中权限过滤器的初始化流程和实现原理

本篇是Shiro系列第三篇,Shiro中的过滤器初始化流程和实现原理。Shiro基于URL的权限控制是通过Filter实现的,本篇从我们注入的ShiroFilterFactoryBean开始入手,翻看源码追寻Shiro中的过滤器的实现原理。


初始化流程

ShiroFilterFactoryBean实现了FactoryBean接口,那么Spring在初始化的时候必然会调用ShiroFilterFactoryBean的getObject()获取实例,而ShiroFilterFactoryBean也在此时做了一系列初始化操作。

关于FactoryBean的介绍和实现方式另外也记了一篇:https://www.guitu18.com/post/2019/04/28/33.html

在getObject()中会调用createInstance(),初始化相关的东西都在这里了,代码贴过来去掉了注释和校验相关的代码。

    protected AbstractShiroFilter createInstance() throws Exception {
        SecurityManager securityManager = getSecurityManager();
        FilterChainManager manager = createFilterChainManager();
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);
        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }

这里面首先获取了我们在ShiroConfig中注入好参数的SecurityManager,再次强调,这位是Shiro中的核心组件。然后创建了一个FilterChainManager,这个类看名字就知道是用来管理和操作过滤器执行链的,我们来看它的创建方法createFilterChainManager()。

    protected FilterChainManager createFilterChainManager() {
        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        Map<String, Filter> defaultFilters = manager.getFilters();
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }
        Map<String, Filter> filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                manager.addFilter(name, filter, false);
            }
        }
        Map<String, String> chains = getFilterChainDefinitionMap();
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                manager.createChain(url, chainDefinition);
            }
        }
        return manager;
    }

第一步new了一个DefaultFilterChainManager,在它的构造方法中将filters和filterChains两个成员变量都初始化为一个能保持插入顺序的LinkedHashMap了,之后再调用addDefaultFilters()添加Shiro内置的一些过滤器。

    public DefaultFilterChainManager() {
        this.filters = new LinkedHashMap<String, Filter>();
        this.filterChains = new LinkedHashMap<String, NamedFilterList>();
        addDefaultFilters(false);
    }
    protected void addDefaultFilters(boolean init) {
        for (DefaultFilter defaultFilter : DefaultFilter.values()) {
            addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
        }
    }

这里用枚举列出了所有Shiro内置过滤器的实例。

public enum DefaultFilter {
    anon(AnonymousFilter.class),
    authc(FormAuthenticationFilter.class),
    authcBasic(BasicHttpAuthenticationFilter.class),
    logout(LogoutFilter.class),
    noSessionCreation(NoSessionCreationFilter.class),
    perms(PermissionsAuthorizationFilter.class),
    port(PortFilter.class),
    rest(HttpMethodPermissionFilter.class),
    roles(RolesAuthorizationFilter.class),
    ssl(SslFilter.class),
    user(UserFilter.class);
}

以上代码有省略,上面列出的枚举类型正好对应Shiro第一篇提到的Shiro内置的一些过滤器,这些过滤器正好是在这里初始化并添加到过滤器执行链中的,每个过滤器都有不同的功能,我们常用的其实只有前面两个。

回到上上一步中,DefaultFilterChainManager初始化完成后,遍历了每一个默认的过滤器并调用了applyGlobalPropertiesIfNecessary()设置一些必要的全局属性。

    private void applyGlobalPropertiesIfNecessary(Filter filter) {
        applyLoginUrlIfNecessary(filter);
        applySuccessUrlIfNecessary(filter);
        applyUnauthorizedUrlIfNecessary(filter);
    }

在这个方法中调用了三个方法,三个方法逻辑是一样的,分别是设置loginUrl、successUrl和unauthorizedUrl,我们就看第一个applyLoginUrlIfNecessary()。

    private void applyLoginUrlIfNecessary(Filter filter) {
        String loginUrl = getLoginUrl();
        if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
            AccessControlFilter acFilter = (AccessControlFilter) filter;
            String existingLoginUrl = acFilter.getLoginUrl();
            if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
                acFilter.setLoginUrl(loginUrl);
            }
        }
    }

看方法名就知道是要设置loginUrl,如果我们配置了loginUrl,那么会将AccessControlFilter中默认的loginUrl替换为我们设置的值,默认的loginUrl为/login.jsp。后面两个方法道理一样,都是将我们设置的参数替换进去,只不过第三个认证失败跳转URL的默认值为null。

继续回到上一步,Map<String, Filter> filters = getFilters(); 这里是获取我们自定义的过滤器,默认是为空的,如果我们配置了自定义的过滤器,那么会将其添加到filters中。至此filters中包含着Shiro内置的过滤器和我们配置的所有过滤器。

下一步,遍历filterChainDefinitionMap,这个filterChainDefinitionMap就是我们在ShiroConfig中注入进去的拦截规则配置。这里是根据我们配置的过滤器规则创建创建过滤器执行链。

    public void createChain(String chainName, String chainDefinition) {
        String[] filterTokens = splitChainDefinition(chainDefinition);
        for (String token : filterTokens) {
            String[] nameConfigPair = toNameConfigPair(token);
            addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
        }
    }

chainName是我们配置的过滤路径,chainDefinition是该路径对应的过滤器,通常我们都是一对一的配置,比如:filterMap.put("/login", "anon");,但看到这个方法我们知道了一个过滤路径其实是可以通过传入["filter1","filter2"...]配置多个过滤器的。在这里会根据我们配置的过滤路径和过滤器映射关系一步步配置过滤器执行链。

    public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
        Filter filter = getFilter(filterName);
        applyChainConfig(chainName, filter, chainSpecificFilterConfig);
        NamedFilterList chain = ensureChain(chainName);
        chain.add(filter);
    }

先从filters中根据filterName获取对应过滤器,然后ensureChain()会先从filterChains根据chainName获取NamedFilterList,获取不到就创建一个并添加到filterChains然后返回。

    protected NamedFilterList ensureChain(String chainName) {
        NamedFilterList chain = getChain(chainName);
        if (chain == null) {
            chain = new SimpleNamedFilterList(chainName);
            this.filterChains.put(chainName, chain);
        }
        return chain;
    }

因为过滤路径和过滤器是一对多的关系,所以ensureChain()返回的NamedFilterList其实就是一个有着name称属性的List<Filter>,这个name保存的就是过滤路径,List保存着我们配置的过滤器。获取到NamedFilterList后在将过滤器加入其中,这样过滤路径和过滤器映射关系就初始化好了。

至此,createInstance()中的createFilterChainManager()才算执行完成,它返回了一个FilterChainManager实例。之后再将这个FilterChainManager注入PathMatchingFilterChainResolver中,它是一个过滤器执行链解析器。

PathMatchingFilterChainResolver中的方法不多,最为重要的是这个getChain()方法。

    public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }
        String requestURI = getPathWithinApplication(request);
        for (String pathPattern : filterChainManager.getChainNames()) {
            if (pathMatches(pathPattern, requestURI)) {
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }
        return null;
    }

看到形参中ServletRequest和ServletResponse这两个参数是不是感觉特别亲切,终于看到了点熟悉的东西了,一看就知道肯定跟请求有关。是的,我们每次请求服务器都会调用这个方法,根据请求的URL去匹配过滤器执行链中的过滤路径,匹配上了就返回其对应的过滤器进行过滤。

这个方法中的filterChainManager.getChainNames()返回的是根据我们的配置配置生成的执行链的过滤路径集合,执行链生成的顺序跟我们的配置的顺序相同。从前文中我们也可以看到,在DefaultFilterChainManager的构造方法中将filterChains初始化为一个LinkedHashMap。所以在我的Shiro笔记第一篇中提到要将范围大的过滤器放在后面就是这个道理,如果第一个匹配的过滤路径就是/**那后面的过滤器永远也匹配不上。


过滤实现原理

那么这个getChain()是如何被调用的呢?既然是HTTP请求那肯定是从Tomcat过来的,当一个请求到达Tomcat时,Tomcat以责任链的形式调用了一系列Filter,OncePerRequestFilter就是众多Filter中的一个。它所实现的doFilter()方法调用了自身的抽象方法doFilterInternal(),这个方法在它的子类AbstractShiroFilter中被实现了。

PathMatchingFilterChainResolver.getChain()就是被在doFilterInternal()中被一步步调用的调用的。

    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse,
                                    final FilterChain chain) throws ServletException, IOException {
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
            final Subject subject = createSubject(request, response);
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                }
            });
    }

这里先获获取滤器,然后执行。

    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
    }

获取过滤器方法如下。

    protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;
        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            return origChain;
        }
        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            chain = resolved;
        } else {
        }
        return chain;
    }

通过getFilterChainResolver()就拿到了上面提到的过滤器执行链解析器PathMatchingFilterChainResolver,然后再调用它的getChain()匹配获取过滤器,最终过滤器在executeChain()中被执行。



首发地址:https://www.guitu18.com/post/2019/08/01/45.html

总结

Shiro框架在我们配置的ShiroFilterFactoryBean进行初始化的时候就做了很多初始化操作,将我们配置的过滤器规则一步步添加对应的过滤器到过滤器执行链中,这个执行链最终被放入执行链解析器。当有请求到达Tomcat时,通过Tomcat中的Filter责任链执行流程,最终Shiro所定义的AbstractShiroFilter.doFilter()被执行,那么它会去获取执行链解析器,通过解析器拿到执行链中的过滤器并执行,这样就实现了基于URL的权限过滤。

本文结束,这篇也算是浅入了Shiro源码了解了一下Shiro过滤器的初始化以及执行过程,相比Spring的源码Shiro的源码要简单易懂的很多很多,它没有Spring那么绕。每次看源码的时候,我都有下面这种感觉,特别是看Spring源码的时候这种感觉尤为强烈:

对于框架我们所配置和调用的,永远是浮在水面上的那一点点,不点进去,你永远不知道下边是一个怎样的庞然大物。封装的越好的框架,浮现出来的越少,隐藏的部分就越多。

比如SpringBoot,为什么能通过一个main方法就启动一个项目,web.xml呢,application.properties呢;SpringMVC为什么就需要一个@RequestMapping就能实现从URL到方法的调用;Shiro为什么仅需要@RequiresPermissions就能实现方法级别的权限控制。

学到的越多就越是感觉自己知道的越少,这是一个很矛盾却又真实存在的感觉。不说了该学习了,上面的最后一条方法级别权限控制下一篇写,时间待定,因为最近在项目中刚好遇到数据库优化相关问题,想先去看看MySQL。

原文地址:https://www.cnblogs.com/guitu18/p/11315195.html

时间: 2024-10-08 01:37:06

Shiro权限管理框架(三):Shiro中权限过滤器的初始化流程和实现原理的相关文章

shiro基础学习(三)&mdash;shiro授权

一.入门程序 1.授权流程        2.授权的三种方式 (1)编程式: 通过写if/else 授权代码块完成. Subject subject = SecurityUtils.getSubject(); if(subject.hasRole("admin")) {      //有权限 } else {      //无权限 } (2)注解式: 通过在执行的Java方法上放置相应的注解完成. @RequiresRoles("admin") public voi

使用AOP思想无侵入式申请权限,解决组件化中权限问题(一)

首先介绍AspectJx使用 https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx 在根项目的build.gradle文件中添加上依赖 dependencies { classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4' } 在app项目的build.gradle里应用插件 apply plugin: 'android-aspect

Shiro权限管理框架详解

https://www.cnblogs.com/jpfss/p/8352031.html 1 权限管理1.1 什么是权限管理 基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源. 权限管理包括用户身份认证和授权两部分,简称认证授权.对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问. 1.2 用户身份认证1.2.1 概念 身份认证,就是判

Shiro权限管理框架(一):Shiro的基本使用

首发地址:https://www.guitu18.com/post/2019/07/26/43.html 核心概念 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.使用Shiro的易于理解的API,您可以快速.轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序. 上面这段话来自百度百科,是不是非常官方,好像说的很明白但是又好像什么都没说的样子,到底是个啥呀.想要快速理解并使用Shiro先要从最重要的三大概念入手. Subject

将 Shiro 作为应用的权限基础 三:基于注解实现的授权认证过程

授权即访问控制,它将判断用户在应用程序中对资源是否拥有相应的访问权限. 如,判断一个用户有查看页面的权限,编辑数据的权限,拥有某一按钮的权限等等. 一.用户权限模型 为实现一个较为灵活的用户权限数据模型,通常把用户信息单独用一个实体表示,用户权限信息用两个实体表示. 用户信息用 LoginAccount 表示,最简单的用户信息可能只包含用户名 loginName 及密码 password 两个属性.实际应用中可能会包含用户是否被禁用,用户信息是否过期等信息. 用户权限信息用 Role 与 Per

项目一:第十二天 1、常见权限控制方式 2、基于shiro提供url拦截方式验证权限 3、在realm中授权 5、总结验证权限方式(四种) 6、用户注销7、基于treegrid实现菜单展示

1 课程计划 1. 常见权限控制方式 2. 基于shiro提供url拦截方式验证权限 3. 在realm中授权 4. 基于shiro提供注解方式验证权限 5. 总结验证权限方式(四种) 6. 用户注销 7. 基于treegrid实现菜单展示 2 常见的权限控制方式 2.1 url拦截实现权限控制 shiro基于过滤器实现的   2.2 注解方式实现权限控制 底层:代理技术     3 基于shiro的url拦截方式验权   <!-- 配置过滤器工厂 --> <bean id="

简单两步快速实现shiro的配置和使用,包含登录验证、角色验证、权限验证以及shiro登录注销流程(基于spring的方式,使用maven构建)

前言: shiro因为其简单.可靠.实现方便而成为现在最常用的安全框架,那么这篇文章除了会用简洁明了的方式讲一下基于spring的shiro详细配置和登录注销功能使用之外,也会根据惯例在文章最后总结一下shiro的大致配置使用流程,希望本篇文章能够后能给大家一种原来shiro这么简单的错觉感觉. 注意:该篇文章的开始是建立在一个完备的spring+mybatis的开发环境中,除了shiro之外的配置基本不会涉及到.做好自己--eguid原创文章 一.依赖的jar包 本篇文章使用shiro-1.4

权限框架之Shiro详解

文章大纲 一.权限框架介绍二.Shiro基础介绍三.Spring Boot整合Shiro代码实战四.项目源码与资料下载五.参考文章 一.权限框架介绍 1. 什么是权限管理   权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源.  权限管理包括用户身份认证和授权两部分,简称认证授权.对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问.1.1 用户身份认证  身份认证,就是判断一个用

Spring Boot + Shiro 实现前后端分离、权限控制

本文总结自实习中对项目的重构.原先项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Freemarker模版的ModelAndView,逐渐有了前后端分离的想法,由于之前,没有接触过,主要参考的还是网上的一些博客教程等,初步完成了前后端分离,在此记录以备查阅. 一.前后端分离思想 前端从后端剥离,形成一个前端工程,前端只利用Json来和后端进行交互,后端不返回页面,只返回Json数据.前后端之间完全通过public A