shiro拦截器源码分析

首先了解filter的工作原理,参考博文https://www.iteye.com/blog/jinnianshilongnian-1736348

挑选一个内置的拦截器开始讲解

 

 

 下面是shiro的基础拦截器类以及张开涛博文的讲解

 

 

 

1、NameableFilter

NameableFilter给Filter起个名字,如果没有设置默认就是FilterName;还记得之前的如authc吗?当我们组装拦截器链时会根据这个名字找到相应的拦截器实例;

 

2、OncePerRequestFilter

OncePerRequestFilter用于防止多次执行Filter的;也就是说一次请求只会走一次拦截器链;另外提供enabled属性,表示是否开启该拦截器实例,默认enabled=true表示开启,如果不想让某个拦截器工作,可以设置为false即可。

 

3、ShiroFilter

ShiroFilter是整个Shiro的入口点,用于拦截需要安全控制的请求进行处理,这个之前已经用过了。

 

4、AdviceFilter

AdviceFilter提供了AOP风格的支持,类似于SpringMVC中的Interceptor:

preHandler:类似于AOP中的前置增强;在拦截器链执行之前执行;如果返回true则继续拦截器链;否则中断后续的拦截器链的执行直接返回;进行预处理(如基于表单的身份验证、授权)

postHandle:类似于AOP中的后置返回增强;在拦截器链执行完成后执行;进行后处理(如记录执行时间之类的);

afterCompletion:类似于AOP中的后置最终增强;即不管有没有异常都会执行;可以进行清理资源(如接触Subject与线程的绑定之类的);

 

5、PathMatchingFilter

PathMatchingFilter提供了基于Ant风格的请求路径匹配功能及拦截器参数解析的功能,如“roles[admin,user]”自动根据“,”分割解析到一个路径参数配置并绑定到相应的路径:

pathsMatch:该方法用于path与请求路径进行匹配的方法;如果匹配返回true;

onPreHandle:在preHandle中,当pathsMatch匹配一个路径后,会调用opPreHandler方法并将路径绑定参数配置传给mappedValue;然后可以在这个方法中进行一些验证(如角色授权),如果验证失败可以返回false中断流程;默认返回true;也就是说子类可以只实现onPreHandle即可,无须实现preHandle。如果没有path与请求路径匹配,默认是通过的(即preHandle返回true)。

 

6、AccessControlFilter

AccessControlFilter提供了访问控制的基础功能;比如是否允许访问/当访问拒绝时如何处理等:

isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false;

onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。

 

onPreHandle会自动调用这两个方法决定是否继续处理:

从shiroflter开始,这是shiro应用的入口

 

 

 这里面只有一个初始方法,可以看到所有的数据都从WebEnvironment中获取,拦截器的加载和shiro的初始化都与它有关,我们看看

而WebEnvironment是如何创建出来的呢,不放太多图,

 

WebEnvironment的创建在EnvironmentLoaderListener,

这里面只有两个方法

void contextInitialized(ServletContextEvent sce); // 当容器启动时调用
void contextDestroyed(ServletContextEvent sce); // 当容器关闭时调用

作用就是在容器启动时创建WebEnvironment对象,这里起作用的是他的父类,注意看这个方法,原文参考https://blog.csdn.net/huangbaokang/article/details/77575331

 // 可在 web.xml 的 context-param 中定义 WebEnvironment 接口的实现类(默认为 IniWebEnvironment) 
public static final String ENVIRONMENT_CLASS_PARAM = "shiroEnvironmentClass";
// 可在 web.xml 的 context-param 中定义 Shiro 配置文件的位置
    public static final String CONFIG_LOCATIONS_PARAM = "shiroConfigLocations";
 // 在 ServletContext 中存放 WebEnvironment 的 key
    public static final String ENVIRONMENT_ATTRIBUTE_KEY = EnvironmentLoader.class.getName() + ".ENVIRONMENT_ATTRIBUTE_KEY";

    private static final Logger log = LoggerFactory.getLogger(EnvironmentLoader.class);


// 从 ServletContext 中获取相关信息,并创建 WebEnvironment 实例
public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException {
       //确保webenvironment只创建一次
        if (servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY) != null) {
            String msg = "There is already a Shiro environment associated with the current ServletContext.  " +
                    "Check if you have multiple EnvironmentLoader* definitions in your web.xml!";
            throw new IllegalStateException(msg);
        }

        servletContext.log("Initializing Shiro environment");
        log.info("Starting Shiro environment initialization.");

        long startTime = System.currentTimeMillis();

        try {
//创建webenviroment实例 WebEnvironment environment
= createEnvironment(servletContext);
//在servletContext中存放webenviroment实例 servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, environment); log.debug(
"Published WebEnvironment as ServletContext attribute with name [{}]", ENVIRONMENT_ATTRIBUTE_KEY); if (log.isInfoEnabled()) { long elapsed = System.currentTimeMillis() - startTime; log.info("Shiro environment initialized in {} ms.", elapsed); } return environment; } catch (RuntimeException ex) { log.error("Shiro environment initialization failed", ex); servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex); throw ex; } catch (Error err) { log.error("Shiro environment initialization failed", err); servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err); throw err; } }
 protected WebEnvironment createEnvironment(ServletContext sc) {
        // 确定 WebEnvironment 接口的实现类
        Class<?> clazz = determineWebEnvironmentClass(sc);

        // 确保该实现类实现了 MutableWebEnvironment 接口
        if (!MutableWebEnvironment.class.isAssignableFrom(clazz)) {
            throw new ConfigurationException();
        }

        // 从 ServletContext 中获取 Shiro 配置文件的位置参数,并判断该参数是否已定义
        String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM);
        boolean configSpecified = StringUtils.hasText(configLocations);

        // 若配置文件位置参数已定义,则需确保该实现类实现了 ResourceConfigurable 接口
        if (configSpecified && !(ResourceConfigurable.class.isAssignableFrom(clazz))) {
            throw new ConfigurationException();
        }

        // 通过反射创建 WebEnvironment 实例,将其转型为 MutableWebEnvironment 类型,并将 ServletContext 放入该实例中
        MutableWebEnvironment environment = (MutableWebEnvironment) ClassUtils.newInstance(clazz);
        environment.setServletContext(sc);

        // 若配置文件位置参数已定义,且该实例是 ResourceConfigurable 接口的实例(实现了该接口),则将此参数放入该实例中
        if (configSpecified && (environment instanceof ResourceConfigurable)) {
            ((ResourceConfigurable) environment).setConfigLocations(configLocations);
        }

        // 可进一步定制 WebEnvironment 实例(在子类中扩展)
        customizeEnvironment(environment);

        // 调用 WebEnvironment 实例的 init 方法
        LifecycleUtils.init(environment);

        // 返回 WebEnvironment 实例
        return environment;
    }

可能顺序有点乱,别急,在创建时会调用determineWebEnvironmentClass在servletContext中寻找是否配置了shiroConfigLocations对象

 

 

 如果没有会默认创建一个IniWebEnviroment对象,这里我们直接看默认方法

public void init() {
        Ini ini = getIni();

        String[] configLocations = getConfigLocations();

        if (log.isWarnEnabled() && !CollectionUtils.isEmpty(ini) &&
                configLocations != null && configLocations.length > 0) {
            log.warn("Explicit INI instance has been provided, but configuration locations have also been " +
                    "specified.  The {} implementation does not currently support multiple Ini config, but this may " +
                    "be supported in the future. Only the INI instance will be used for configuration.",
                    IniWebEnvironment.class.getName());
        }

        if (CollectionUtils.isEmpty(ini)) {
            log.debug("Checking any specified config locations.");
            ini = getSpecifiedIni(configLocations);
        }

        if (CollectionUtils.isEmpty(ini)) {
            log.debug("No INI instance or config locations specified.  Trying default config locations.");
            ini = getDefaultIni();
        }

        if (CollectionUtils.isEmpty(ini)) {
            String msg = "Shiro INI configuration was either not found or discovered to be empty/unconfigured.";
            throw new ConfigurationException(msg);
        }

        setIni(ini);

        configure();
    }

这个初始方法会调用configure,它会默认帮你创建WebSecurityManager和FilterChainResolver

主要看shiro的这个filterChain,它是自定义拦截器的关键

 

 如果你配置了相关filter和url则会使用IniFilterChainResolverFactory的getInstance方法初始化一个FilterChainResolver。

这个创建过程除了加载自定义的拦截器还会加载默认的filter

一直追溯到

 

 到这里,shiro的初始化也就完成,拦截器也全部加载完毕

然后结合默认的拦截器看看shiro中拦截器的具体执行过程

这里我选择userFilter

请求发起之后spring会寻找配置的shiro拦截器,将请求交由它处理,具体操作在DelegatingFilterProxy、

首先找到代理bean,

 

 

 

 可以看看这个bean是哪里来的,之前讲了WebEnvironment容器初始化是会初始化一个FilterChainResolver

DelegatingFilterProxy会调用getbean来寻找符合的bean

 

 

 实现追溯很长直接放出来,这个beanName就是ShiroFilter,可以看到这里调用了getobject方法,直接寻找shiro里面的实现

 

 

 就在ShiroFilterFactoryBean中

 

 

 这个instance就是AbstractShiroFilter,所以返回的也是这个对象

 ShiroFilterFactoryBean返回的对象是AbstractShiroFilter这个类很关键还包含了subject创建,后面说,然后调用代理对象执行dofilter()

 

但是AbstractShiroFilter也没有重写dofilter(),所以会调用OncePerRequestFilter的dofilter,这个dofilter是为了防止拦截器多次执行,然后调用子类AbstractShiroFilter的

doFilterInternal,执行的excuteChain

 这里面有个获取拦截链FilterChain的方法是getExecutionChain

protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        
        FilterChain chain = origChain;
       
        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }
       //获取所有shiro的代理filter
        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            log.trace("Resolved a configured FilterChain for the current request.");
            chain = resolved;
        } else {
            log.trace("No FilterChain configured for the current request.  Using the default.");
        }

        return chain;
    }

执行chain.dofilter()这里的chain可以是你的自定义拦截器或者shiro内置的拦截器,打开一个内置的拦截器

 

 

 这里面继承了AccessControllerFilter的两个方法

isAccessAllowed:表示是否允许访问

onAccessDenied:表示当访问拒绝时是否已经处理了

在这个AccessControllerFilter里面onPreHandle会自动调用这两个方法

继续上面的dofilter方法,可以看见拦截器里面是没有实现的,所以会寻找它的父类实现,还是这个OncePerRequestFilter

还是看看这个方法

 

 

 这里会根据你传入的filterChain调用对应子类的实现,这里是在adviceChain

 

 

 这里就会调用preHandle来决定请求继续运行或者失败,子类实现在pathmatchingfilter中,执行是调用onPreHandle,就是上面讲的,决定请求的下一步

isAccessAllowed会判断是否已经登录,成功会继续执行下面的拦截链,否则会交由onAccessDenied处理

 

 ok,差不多这里就完了,还有什么要补充的话,留言给我就行

 

 

 

 

FilterChainResolver

posted @ 2020-04-27 19:44  BrightFl  阅读(628)  评论(0编辑  收藏  举报