shiro原理之过滤器

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了shiro原理之过滤器相关的知识,希望对你有一定的参考价值。

参考技术A 这几天一直在研究Shiro到底是如何工作的,即一个请求过来了,它是如何做到知道这个请求应该用什么方式来鉴权的?应该调用哪个过滤器?自己定义的过滤器该如何才能生效?

带着这样的疑问,我做了一些测试与研究,并记录于此文。

Shiro对于请求的鉴权的实现也是通过过滤器(或者说是拦截器)来实现的,但是Spring项目中有拦截链机制,会有多个拦截器生效,包括系统内置的以及Shiro注入的,所以需要搞懂他的过滤的实现机制就需要去弄明白这些过滤器是如何过滤的。

在 ApplicationFilterChain 的 doFilter 方法下打上断点

默认过滤器

我们来看看 AbstractShiroFilter 的部分源码:

返回的是一个 ProxiedFilterChain 的实例,该实例包含了 PathMatchingFilterChainResolver 所匹配出来的过滤器,如下图,匹配的是系统内置的名为 anon 过滤器,至于它所对应的过滤器是什么可以见上面默认过滤器的表。

接着,在 AbstractShiroFilter 中的 executeChain 就会执行它的 doFilter 方法。

刚开始学习看源码,IDEA的调试工具也不是很会用,好在确实IDEA很强大,不然这么多过滤链跳来跳去是真的难懂。经过这次的阅读以及网上的博客的研究,Shiro的过滤链如果有自定义的过滤链的话,一定不能像平常的拦截器那样注入,必须要在注入 ShiroFilterFactoryBean 时使用如下方式注入才能生效。

因为很可能像平常注入过滤器那样注入先后顺序可能会存在问题。

Shiro之@RequiresPermissions注解原理详解

前言

shiro为我们提供了几个权限注解,如下图:

这几个注解原理都类似,这里我们讲解@RequiresPermissions的原理。

铺垫

第一
首先要清楚@RequiresPermissions的基本用法。就是在Controller的方法里,加上@RequiresPermissions注解,并写上权限标识。那么,有该权限标识的用户,才能访问到请求。如下图:

第二
先剧透一下,@RequiresPermissions注解的原理是使用了Spring的AOP进行了增强。来判断当前用户是否有该权限标识。我们复习一下Spring的AOP原理。AOP的核心流程是ReflectiveMethodInvocation类中的proceed方法。该方法会遍历加强集合,执行每一个加强的方法。不熟悉的可以查看这篇博客:《Spring之Joinpoint类详解》

执行流程分析

有了上面的铺垫,我们开始从源码分析其执行流程。
首先,断点打到ReflectiveMethodInvocation类的proceed方法这里,因为要对代理对象进行增强,必定要走这里。然后发送请求,进入断点,我们先看加强链上有哪些元素:

第一个Expose开头的对象,是Spring自己生成的元素,我们无需关注。第二个元素,就是加强@RequiresPermissions而生成的元素。看该类的源码:

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.shiro.spring.security.interceptor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.shiro.aop.AnnotationResolver;
import org.apache.shiro.authz.aop.*;
import org.apache.shiro.spring.aop.SpringAnnotationResolver;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Allows Shiro Annotations to work in any <a href="http://aopalliance.sourceforge.net/">AOP Alliance</a>
 * specific implementation environment (for example, Spring).
 *
 * @since 0.2
 */
public class AopAllianceAnnotationsAuthorizingMethodInterceptor
        extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor 

    public AopAllianceAnnotationsAuthorizingMethodInterceptor() 
        List<AuthorizingAnnotationMethodInterceptor> interceptors =
                new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);

        //use a Spring-specific Annotation resolver - Spring's AnnotationUtils is nicer than the
        //raw JDK resolution process.
        AnnotationResolver resolver = new SpringAnnotationResolver();
        //we can re-use the same resolver instance - it does not retain state:
        interceptors.add(new RoleAnnotationMethodInterceptor(resolver));
        interceptors.add(new PermissionAnnotationMethodInterceptor(resolver));
        interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver));
        interceptors.add(new UserAnnotationMethodInterceptor(resolver));
        interceptors.add(new GuestAnnotationMethodInterceptor(resolver));

        setMethodInterceptors(interceptors);
    
    /**
     * Creates a @link MethodInvocation MethodInvocation that wraps an
     * @link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation instance,
     * enabling Shiro Annotations in <a href="http://aopalliance.sourceforge.net/">AOP Alliance</a> environments
     * (Spring, etc).
     *
     * @param implSpecificMethodInvocation AOP Alliance @link org.aopalliance.intercept.MethodInvocation MethodInvocation
     * @return a Shiro @link MethodInvocation MethodInvocation instance that wraps the AOP Alliance instance.
     */
    protected org.apache.shiro.aop.MethodInvocation createMethodInvocation(Object implSpecificMethodInvocation) 
        final MethodInvocation mi = (MethodInvocation) implSpecificMethodInvocation;

        return new org.apache.shiro.aop.MethodInvocation() 
            public Method getMethod() 
                return mi.getMethod();
            

            public Object[] getArguments() 
                return mi.getArguments();
            

            public String toString() 
                return "Method invocation [" + mi.getMethod() + "]";
            

            public Object proceed() throws Throwable 
                return mi.proceed();
            

            public Object getThis() 
                return mi.getThis();
            
        ;
    

    /**
     * Simply casts the method argument to an
     * @link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation and then
     * calls <code>methodInvocation.@link org.aopalliance.intercept.MethodInvocation#proceed proceed()</code>
     *
     * @param aopAllianceMethodInvocation the @link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation
     * @return the @link org.aopalliance.intercept.MethodInvocation#proceed() org.aopalliance.intercept.MethodInvocation.proceed() method call result.
     * @throws Throwable if the underlying AOP Alliance <code>proceed()</code> call throws a <code>Throwable</code>.
     */
    protected Object continueInvocation(Object aopAllianceMethodInvocation) throws Throwable 
        MethodInvocation mi = (MethodInvocation) aopAllianceMethodInvocation;
        return mi.proceed();
    

    /**
     * Creates a Shiro @link MethodInvocation MethodInvocation instance and then immediately calls
     * @link org.apache.shiro.authz.aop.AuthorizingMethodInterceptor#invoke super.invoke.
     *
     * @param methodInvocation the AOP Alliance-specific <code>methodInvocation</code> instance.
     * @return the return value from invoking the method invocation.
     * @throws Throwable if the underlying AOP Alliance method invocation throws a <code>Throwable</code>.
     */
    public Object invoke(MethodInvocation methodInvocation) throws Throwable 
        org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
        return super.invoke(mi);
    


可以看出,这个类实现了MethodInterceptor接口。那么,其invoke方法,就是对原对象方法的增强。点进invoke方法,最后调到了AnnotationsAuthorizingMethodInterceptor类的assertAuthorized方法,如下:

protected void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException 
        //default implementation just ensures no deny votes are cast:
        Collection<AuthorizingAnnotationMethodInterceptor> aamis = getMethodInterceptors();
        if (aamis != null && !aamis.isEmpty()) 
            for (AuthorizingAnnotationMethodInterceptor aami : aamis) 
                if (aami.supports(methodInvocation)) 
                    aami.assertAuthorized(methodInvocation);
                
            
        
    

在这里,会遍历是否有前言中截图的那几个注解,如果有的话,则调用相应的注解Handler去处理相应的逻辑。@RequiresPermissions的Handler的处理逻辑就是调用Realm中定义的权限获取方法,判断是否有注解中的标识,具体的调用Realm流程不再解析,前面博客已经解析过。
这样,就对@RequiresPermissions注解的方法进行了增强。

@RequiresPermissions切面

使用AOP,一定要有切面,这样Spring在实例化bean过程中,才会生成代理对象和加强链。Shiro的切面是AuthorizationAttributeSourceAdvisor类,需要加入Spring容器中管理,配置如下:

 /**
     * 开启Shiro注解通知器
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
            @Qualifier("securityManager") SecurityManager securityManager)
    
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    

下面分析这个类源码。前面博客说过,Advisor切面的两大核心就是切点pointcut和增强advice。我们看AuthorizationAttributeSourceAdvisor 的这两个核心在哪儿:

 /**
     * Create a new AuthorizationAttributeSourceAdvisor.
     */
    public AuthorizationAttributeSourceAdvisor() 
        setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
    

这里设置了增强advice。正是我们上面分析的AopAllianceAnnotationsAuthorizingMethodInterceptor类。

public boolean matches(Method method, Class targetClass) 
        Method m = method;

        if ( isAuthzAnnotationPresent(m) ) 
            return true;
        

        //The 'method' parameter could be from an interface that doesn't have the annotation.
        //Check to see if the implementation has it.
        if ( targetClass != null) 
            try 
                m = targetClass.getMethod(m.getName(), m.getParameterTypes());
                return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass);
             catch (NoSuchMethodException ignored) 
                //default return value is false.  If we can't find the method, then obviously
                //there is no annotation, so just use the default return value.
            
        

        return false;
    

matches方法定义了切点。就是在对@RequiresPermissions等注解收集,然后判断是不是切点。这样,切面就分析完了。

总结

shiro定义了AuthorizationAttributeSourceAdvisor 切面,在切面里,定义了AopAllianceAnnotationsAuthorizingMethodInterceptor增强,来查询Realm中用户的权限等信息,判断是否和参数中的数据匹配。定义了matches方法,来收集@RequiresPermissions等注解。
在Spring实例化Bean时,执行BeanPostProcessor的postProcessAfterInitialization方法生成代理对象时(循环依赖的情况除外),就会找到shiro定义的切面,生成相应的代理对象,当执行方法时,就会走增强的方法,进行方法增强。
所以对于shiro框架的使用者而言,只需将AuthorizationAttributeSourceAdvisor 切面加入到Spring容器,然后在Realm中定义用户的权限查询方法。然后在需要的方法上加@RequiresPermissions注解,就可以进行权限的控制了。

以上是关于shiro原理之过滤器的主要内容,如果未能解决你的问题,请参考以下文章

spring 集成shiro 之 自定义过滤器

spring 集成shiro 之 自定义过滤器

Apache Shiro 的Web应用支持指南

springsecurity-shiro获取登录用户详解

Shiro源码——shiro提供默认过滤器详解

shiro的过滤器和web服务器的过滤器哪个先调用