Shiro框架Web环境下过滤器结构分析

来源:互联网 发布:网络7层协议 编辑:程序博客网 时间:2024/04/30 11:41

Shiro的过滤器的配置是结合使用Spring的DelegatingFilterProxy与FactoryBean2种技术来完成自身过滤器的植入的,所以理解Shiro的过滤器首先要理解这2者的使用。


1. DelegatingFilterProxy

Spring提供的一个简便的过滤器的处理方案,它将具体的操作交给内部的Filter对象delegate去处理,而这个delegate对象通过Spring IOC容器获取,这里采用的是Spring的FactoryBean的方式获取这个对象。

DelegatingFilterProxy的配置如下

[html] view plaincopyprint?
  1. <filter>   
  2.     <filter-name>shiroFilter</filter-name>  
  3.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  4.     <init-param>  
  5.          <param-name>targetFilterLifecycle</param-name>   
  6.          <param-value>true</param-value>   
  7.     </init-param>   
  8. </filter>  

虽然只配置了这一个filter,但是它并做任何实际的工作,而是把工作交由Spring中容器为bean的名字shiroFilter的类,即ShiroFilterFactoryBean;


2. ShiroFilterFactoryBean

配置如下

[html] view plaincopyprint?
  1. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  2.     ..  
  3. </bean>  

由于它是个FactroyBean,所以上面的delegate真正的对象是通过它的getObject()获取的。

这里是FactoryBean接口获取实例的标准方法

[html] view plaincopyprint?
  1. public Object getObject() throws Exception {  
  2.         if (instance == null) {  
  3.             instance = createInstance();  
  4.         }  
  5.         return instance;  
  6.     }  

这里是真正创建对象的方法

[html] view plaincopyprint?
  1. protected AbstractShiroFilter createInstance() throws Exception {  
  2.   
  3.         log.debug("Creating Shiro Filter instance.");  
  4.   
  5.         SecurityManager securityManager = getSecurityManager();  
  6.         if (securityManager == null) {  
  7.             String msg = "SecurityManager property must be set.";  
  8.             throw new BeanInitializationException(msg);  
  9.         }  
  10.   
  11.         if (!(securityManager instanceof WebSecurityManager)) {  
  12.             String msg = "The security manager does not implement the WebSecurityManager interface.";  
  13.             throw new BeanInitializationException(msg);  
  14.         }  
  15.   
  16.         FilterChainManager manager = createFilterChainManager();  
  17.   
  18.         //Expose the constructed FilterChainManager by first wrapping it in a  
  19.         // FilterChainResolver implementation. The AbstractShiroFilter implementations  
  20.         // do not know about FilterChainManagers - only resolvers:  
  21.         PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();  
  22.         chainResolver.setFilterChainManager(manager);  
  23.   
  24.         //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built  
  25.         //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class  
  26.         //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts  
  27.         //injection of the SecurityManager and FilterChainResolver:  
  28.         return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);  
  29.     }  
所以真正完成实际工作的过滤器是SpringShiroFilter,这个对象才是真正的delegate。


3. SpringShiroFilter: ShiroFilterFactoryBean的内部类,继承AbstractShiroFilter

[html] view plaincopyprint?
  1. private static final class SpringShiroFilter extends AbstractShiroFilter {  
  2.   
  3.         protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {  
  4.             super();  
  5.             if (webSecurityManager == null) {  
  6.                 throw new IllegalArgumentException("WebSecurityManager property cannot be null.");  
  7.             }  
  8.             setSecurityManager(webSecurityManager);  
  9.             if (resolver != null) {  
  10.                 setFilterChainResolver(resolver);  
  11.             }  
  12.         }  
  13.     }  

4. OncePerRequestFilter : AbstractShiroFilter的父类

关键方法

[html] view plaincopyprint?
  1. protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)  
  2.             throws ServletException, IOException;  

这个方法有过滤器中调用:

[html] view plaincopyprint?
  1. public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)  
  2.             throws ServletException, IOException {  
  3.         String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();  
  4.         if (request.getAttribute(alreadyFilteredAttributeName) != null || shouldNotFilter(request)) {  
  5.             log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());  
  6.             // Proceed without invoking this filter...  
  7.             filterChain.doFilter(request, response);  
  8.         } else {  
  9.             // Do invoke this filter...  
  10.             log.trace("Filter '{}' not yet executed.  Executing now.", getName());  
  11.             request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);  
  12.   
  13.             try {  
  14.                 doFilterInternal(request, response, filterChain);  
  15.             } finally {  
  16.                 // Once the request has finished, we're done and we don't  
  17.                 // need to mark as 'already filtered' any more.  
  18.                 request.removeAttribute(alreadyFilteredAttributeName);  
  19.             }  
  20.         }  
  21.     }  
doFilterInternal这个方法有2处实现,1是AbstractShiroFilter的实现,2是AdviceFilter的实现。通过查看shiro的内定义的Filter继承结构可以看出,除了SpringShiroFilter这个内部类是继承前者,其他所有的用到的Filter都是继承后者。SpringShiroFilter是每次请求的第一个真正处理实际工作的Filter(主要是创建一个Subject并绑定相关数据)。

5. AbstractShiroFilter:OncePerRequestFilter的第一个子类

[html] view plaincopyprint?
  1. protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)  
  2.             throws ServletException, IOException {  
  3.   
  4.         Throwable t = null;  
  5.   
  6.         try {  
  7.             final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);  
  8.             final ServletResponse response = prepareServletResponse(request, servletResponse, chain);  
  9.   
  10.             final Subject subject = createSubject(request, response);  
  11.   
  12.             //noinspection unchecked  
  13.             subject.execute(new Callable() {  
  14.                 public Object call() throws Exception {  
  15.                     updateSessionLastAccessTime(request, response);  
  16.                     executeChain(request, response, chain);  
  17.                     return null;  
  18.                 }  
  19.             });  
  20.         } catch (ExecutionException ex) {  
  21.             t = ex.getCause();  
  22.         } catch (Throwable throwable) {  
  23.             t = throwable;  
  24.         }  
  25.   
  26.         if (t != null) {  
  27.             if (t instanceof ServletException) {  
  28.                 throw (ServletException) t;  
  29.             }  
  30.             if (t instanceof IOException) {  
  31.                 throw (IOException) t;  
  32.             }  
  33.             //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:  
  34.             String msg = "Filtered request failed.";  
  35.             throw new ServletException(msg, t);  
  36.         }  
  37.     }  

这段代码表示每次经过AbstractShiroFilter的doFilterInternal方法(具体的类也就是上面的内部类SpringShiroFilter)都会创建一个新的Subject,具体分析里面的代码可以发现,这个Subject的数据会从SubjectContext或Session中获取过来。这意味着每次经过Shiro过滤器的HTTP请求,都会创建一次新的Subject.

Suject里面的数据,主要是从SubjectContext中获取,但是获取方式不一样,如SecurityManager总是从SubjectContext中直接获取,而其他数据则主要从Session中获取。只有在登录操作的时候数据会都从SubjectContext上下文中获取。因为登录成功后还会有一个绑定操作,它会把当前用户的相关信息写入Session中去。

DefaultSecurityManager代码如下:

[html] view plaincopyprint?
  1. protected void bind(Subject subject) {  
  2.         // TODO consider refactoring to use Subject.Binder.  
  3.         // This implementation was copied from SessionSubjectBinder that was removed  
  4.         PrincipalCollection principals = subject.getPrincipals();  
  5.         if (principals != null && !principals.isEmpty()) {  
  6.             Session session = subject.getSession();  
  7.             bindPrincipalsToSession(principals, session);  
  8.         } else {  
  9.             Session session = subject.getSession(false);  
  10.             if (session != null) {  
  11.                 session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);  
  12.             }  
  13.         }  
  14.   
  15.         if (subject.isAuthenticated()) {  
  16.             Session session = subject.getSession();  
  17.             session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, subject.isAuthenticated());  
  18.         } else {  
  19.             Session session = subject.getSession(false);  
  20.             if (session != null) {  
  21.                 session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);  
  22.             }  
  23.         }  
  24.     }  

[html] view plaincopyprint?
  1. private void bindPrincipalsToSession(PrincipalCollection principals, Session session) throws IllegalArgumentException {  
  2.         if (session == null) {  
  3.             throw new IllegalArgumentException("Session argument cannot be null.");  
  4.         }  
  5.         if (CollectionUtils.isEmpty(principals)) {  
  6.             throw new IllegalArgumentException("Principals cannot be null or empty.");  
  7.         }  
  8.         session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, principals);  
  9.     }  

其他登录相关的信息绑定到SubjectContext的操作代码如下,每个set方法的调用都将数据保存到SubjectContext:

[html] view plaincopyprint?
  1. protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {  
  2.         SubjectContext context = createSubjectContext();  
  3.         context.setAuthenticated(true);  
  4.         context.setAuthenticationToken(token);  
  5.         context.setAuthenticationInfo(info);  
  6.         if (existing != null) {  
  7.             context.setSubject(existing);  
  8.         }  
  9.         return createSubject(context);  
  10.     }  


6. AdviceFilter:OncePerRequestFilter的第二个子类 

它是全部的验证与授权Filter的父类,其doFilterInternal方法承担此类过滤器的核心逻辑。

[java] view plaincopyprint?
  1. public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)  
  2.             throws ServletException, IOException {  
  3.   
  4.         Exception exception = null;  
  5.   
  6.         try {  
  7.   
  8.             boolean continueChain = preHandle(request, response);  
  9.             if (log.isTraceEnabled()) {  
  10.                 log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");  
  11.             }  
  12.   
  13.             if (continueChain) {  
  14.                 executeChain(request, response, chain);  
  15.             }  
  16.   
  17.             postHandle(request, response);  
  18.             if (log.isTraceEnabled()) {  
  19.                 log.trace("Successfully invoked postHandle method");  
  20.             }  
  21.   
  22.         } catch (Exception e) {  
  23.             exception = e;  
  24.         } finally {  
  25.             cleanup(request, response, exception);  
  26.         }  
  27.     }  
从上面的代码可以看出,其核心的逻辑是3个部分: preHandle, executeChain,postHandle。后2者都只有该类中有唯一的实现,子类并不覆盖,而preHandle则由一个子类PathMatchingFilter中覆盖,代码如下:

[java] view plaincopyprint?
  1. public boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {  
  2.   
  3.         if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {  
  4.             if (log.isTraceEnabled()) {  
  5.                 log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");  
  6.             }  
  7.             return true;  
  8.         }  
  9.   
  10.         for (String path : this.appliedPaths.keySet()) {  
  11.             // If the path does match, then pass on to the subclass implementation for specific checks  
  12.             //(first match 'wins'):  
  13.             if (pathsMatch(path, request)) {  
  14.                 if (log.isTraceEnabled()) {  
  15.                     log.trace("Current requestURI matches pattern [" + path + "].  Performing onPreHandle check...");  
  16.                 }  
  17.                 Object config = this.appliedPaths.get(path);  
  18.                 return onPreHandle(request, response, config);  
  19.             }  
  20.         }  
  21.   
  22.         //no path matched, allow the request to go through:  
  23.         return true;  
  24.     }  

这个方法根据用户请求的地址是否与该Filter配置的地址匹配来决定是否调用内部的onPreHandler方法。从shiroFilter中的属性filterChainDefinitions配置中可以看出,shiro默认的那些过滤器如user,roles,perms等等都可以统一使用这种方式,对于内部的处理则分别由各个Filter的onPreHandler(其实是由内部的isAccessAllowed和onAccessDenied方法)来决定了。

举2个例子

第一个是AuthenticationFilter的isAccessAllowed方法,它只检测用户是否通过验证

[java] view plaincopyprint?
  1. protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {  
  2.         Subject subject = getSubject(request, response);  
  3.         return subject.isAuthenticated();  
  4.     }  
第二个是RolesAuthorizationFilter的isAccessAllowed方法,它检测用户的角色是否满足
[java] view plaincopyprint?
  1. public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {  
  2.   
  3.        Subject subject = getSubject(request, response);  
  4.        String[] rolesArray = (String[]) mappedValue;  
  5.   
  6.        if (rolesArray == null || rolesArray.length == 0) {  
  7.            //no roles specified, so nothing to check - allow access.  
  8.            return true;  
  9.        }  
  10.   
  11.        Set<String> roles = CollectionUtils.asSet(rolesArray);  
  12.        return subject.hasAllRoles(roles);  
  13.    }  
原创粉丝点击