博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ShiroFilterFactoryBean源码及拦截原理深入分析
阅读量:7032 次
发布时间:2019-06-28

本文共 16284 字,大约阅读时间需要 54 分钟。

hot3.png

本篇文章篇幅比较长,但是细看下去相信对学习Shiro应该会有帮助。好了,闲话不多说,直接进入正题:

Shiro提供了与Web集成的支持,其通过一个ShiroFilter入口来拦截需要安全控制的URL,然后进行相应的控制,ShiroFilter类似于如Strut2/SpringMVC这种web框架的前端控制器,其是安全控制的入口点,其负责读取配置(如ini配置文件),然后判断URL是否需要登录/权限等工作。

而要在Spring中使用Shiro的话,可在web.xml中配置一个DelegatingFilterProxyDelegatingFilterProxy作用是自动到Spring容器查找名字为shiroFilterfilter-name)的bean并把所有Filter的操作委托给它。

首先是在web.xml中配置DelegatingFilterProxy

shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*

配置好DelegatingFilterProxy后,下面只要再把ShiroFilter配置到Spring容器(此处为Spring的配置文件)即可:

可以看到我们使用了ShiroFilterFactoryBean来创建shiroFilter,这里用到了Spring中一种特殊的Bean——FactoryBean。当需要得到名为”shiroFilter“的bean时,会调用其getObject()来获取实例。下面我们通过分析ShiroFilterFactoryBean创建实例的过程来探究Shiro是如何实现安全拦截的:

public Object getObject() throws Exception {        if (instance == null) {            instance = createInstance();        }        return instance;    }

其中调用了createInstance()来创建实例:

protected AbstractShiroFilter createInstance() throws Exception {        // 这里是通过FactoryBean注入的SecurityManager(必须)        SecurityManager securityManager = getSecurityManager();        if (securityManager == null) {            String msg = "SecurityManager property must be set.";            throw new BeanInitializationException(msg);        }        if (!(securityManager instanceof WebSecurityManager)) {            String msg = "The security manager does not implement the WebSecurityManager interface.";            throw new BeanInitializationException(msg);        }        FilterChainManager manager = createFilterChainManager();        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();        chainResolver.setFilterChainManager(manager);        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);    }

可以看到创建SpringShiroFilter时用到了两个组件:SecurityManagerChainResolver

  • SecurityManager:我们知道其在Shiro中的地位,类似于一个“安全大管家”,相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher,是Shiro的心脏,所有具体的交互都通过SecurityManager进行控制,它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
  • ChainResolver:Filter链解析器,用来解析出该次请求需要执行的Filter链。
  • PathMatchingFilterChainResolverChainResolver的实现类,其中还包含了两个重要组件FilterChainManagerPatternMatcher
  • FilterChainManager:管理着Filter和Filter链,配合PathMatchingFilterChainResolver解析出Filter链
  • PatternMatcher:用来进行请求路径匹配,默认为Ant风格的路径匹配

先有一个大体的了解,那么对于源码分析会有不少帮助。下面会对以上两个重要的组件进行分析,包括PathMatchingFilterChainResolverFilterChainManager。首先贴一段ShiroFilter的在配置文件中的定义:

/resources/** = anon /plugin/** = anon /download/** = anon /special/unauthorized = anon /register = anon /login = ssl,authc /logout = logout /admin/** = roles[admin] /** = user

再来看看PathMatchingFilterChainResolverFilterChainManager的创建过程:

protected FilterChainManager createFilterChainManager() {        // 默认使用的FilterChainManager是DefaultFilterChainManager        DefaultFilterChainManager manager = new DefaultFilterChainManager();        // DefaultFilterChainManager默认会注册的filters(后面会列出)        Map
defaultFilters = manager.getFilters(); // 将ShiroFilterFactoryBean配置的一些公共属性(上面配置的loginUrl,successUrl,unauthorizeUrl)应用到默认注册的filter上去 for (Filter filter : defaultFilters.values()) { applyGlobalPropertiesIfNecessary(filter); } // 处理自定义的filter(上面配置的filters属性),步骤类似上面 Map
filters = getFilters(); if (!CollectionUtils.isEmpty(filters)) { for (Map.Entry
entry : filters.entrySet()) { String name = entry.getKey(); Filter filter = entry.getValue(); applyGlobalPropertiesIfNecessary(filter); if (filter instanceof Nameable) { ((Nameable) filter).setName(name); } // 将Filter添加到manager中去,可以看到对于Filter的管理是依赖于FilterChainManager的 manager.addFilter(name, filter, false); } } // 根据FilterChainDefinition的配置来构建Filter链(上面配置的filterChainDefinitions属性) Map
chains = getFilterChainDefinitionMap(); if (!CollectionUtils.isEmpty(chains)) { for (Map.Entry
entry : chains.entrySet()) { String url = entry.getKey(); String chainDefinition = entry.getValue(); // 后面会分析该步的源码,功能上就是创建Filter链 manager.createChain(url, chainDefinition); } } return manager; }

下面有必要来看看DefaultFilterChainManager的源码,分析一下上面调用到的方法。先来看看他的几个重要的属性:

private FilterConfig filterConfig;    private Map
filters; //pool of filters available for creating chains private Map
filterChains; //key: chain name, value: chain

其中filterConfig仅在初始化Filter时有效,而我们自定义的Filter都不是init的,所以该属性可以暂时忽略()。 

而后面两张map就重要了:filters中缓存了所有添加的filter,filterChains则缓存了所有的filterChain。其中前者的key是filter name,value是Filter。而后者的key是chain name,value是NamedFilterList。 
有的童鞋可能会问NamedFilterList是怎么样的结构呢,你可以把它当成List<Filter>,这样就好理解了吧。下面再分析刚才createFilterChainManager()中调用过的manager的几个方法:

  • addFilter(缓存filter让manager来管理)
  • public void addFilter(String name, Filter filter, boolean init) {        addFilter(name, filter, init, true);    }    protected void addFilter(String name, Filter filter, boolean init, boolean overwrite) {        Filter existing = getFilter(name);        if (existing == null || overwrite) {            if (filter instanceof Nameable) {                ((Nameable) filter).setName(name);            }            if (init) {                initFilter(filter);            }            this.filters.put(name, filter);        }    }

    filter缓存到filters这张map里,不管是默认注册的还是自定义的都需要FilterChainManager来统一管理。

  • createChain:创建filterChain并将定义的filter都加进去
  • // chainName就是拦截路径"/resources/**",chainDefinition就是多个过滤器名的字符串    public void createChain(String chainName, String chainDefinition) {        if (!StringUtils.hasText(chainName)) {            throw new NullPointerException("chainName cannot be null or empty.");        }        if (!StringUtils.hasText(chainDefinition)) {            throw new NullPointerException("chainDefinition cannot be null or empty.");        }        if (log.isDebugEnabled()) {            log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]");        }        // 先分离出配置的各个filter,比如         // "authc, roles[admin,user], perms[file:edit]" 分离后的结果是:        // { "authc", "roles[admin,user]", "perms[file:edit]" }        String[] filterTokens = splitChainDefinition(chainDefinition);        // 进一步分离出"[]"内的内容,其中nameConfigPair是一个长度为2的数组        // 比如 roles[admin,user] 经过解析后的nameConfigPair 为{"roles", "admin,user"}        for (String token : filterTokens) {            String[] nameConfigPair = toNameConfigPair(token);            // 得到了 拦截路径、filter以及可能的"[]"中的值,那么执行addToChain            addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);        }    }

    至此,FilterChainManager就创建完了,它无非就是缓存了两张map,没有什么逻辑上的操作。下面将FilterChainManager设置到PathMatchingFilterChainResolver中。PathMatchingFilterChainResolver实现了FilterChainResolver接口,该接口中只定义了一个方法:

  • FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain);

    通过解析请求来得到一个新的FilterChain。而PathMatchingFilterChainResolver实现了该接口,依靠了FilterChainManager中保存的chainFiltersfilters这两张map来根据请求路径解析出相应的filterChain,并且和originalChain组合起来使用。下面具体看看PathMatchingFilterChainResolver中的实现:

    public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {        // 得到 FilterChainManager         FilterChainManager filterChainManager = getFilterChainManager();        if (!filterChainManager.hasChains()) {            return null;        }        String requestURI = getPathWithinApplication(request);        // chainNames就是刚定义的filterChains的keySet,也就是所有的路径集合(比如:["/resources/**","/login"])        for (String pathPattern : filterChainManager.getChainNames()) {            // 请求路径是否匹配某个 定义好的路径:            if (pathMatches(pathPattern, requestURI)) {                if (log.isTraceEnabled()) {                    log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " + "Utilizing corresponding filter chain...");                }                // 找到第一个匹配的Filter链,那么就返回一个ProxiedFilterChain                return filterChainManager.proxy(originalChain, pathPattern);            }        }        return null;    }

    这里返回只有两种情况,要么是null,要么就是一个ProxiedFilterChain。返回null并不表示中断FilterChain,而是只用originChain。而关于ProxiedFilterChain,它实现了FilterChain,内部维护了两份FilterChain(其实一个是FilterChain,另一个是List<Filter>) 

    FilterChain也就是web.xml中注册的Filter形成的FilterChain,我们称之为originChain。而另一个List<Filter>则是我们在Shiro中注册的Filter链了,下面看看ProxiedFilterChain中关于doFilter(...)的实现:

    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {        if (this.filters == null || this.filters.size() == this.index) {            //we've reached the end of the wrapped chain, so invoke the original one:            if (log.isTraceEnabled()) {                log.trace("Invoking original filter chain.");            }            this.orig.doFilter(request, response);        } else {            if (log.isTraceEnabled()) {                log.trace("Invoking wrapped filter at index [" + this.index + "]");            }            this.filters.get(this.index++).doFilter(request, response, this);        }    }

    可以看到,它会先执行Shiro中执行的filter,然后再执行web.xml中的Filter。不过要注意的是,需要等到originChain执行到ShiroFilter之后才会执行Shiro中的Filter链。 

    至此,两个组件的创建过程差不多都介绍完了,那么当这两个组件创建完毕后,是如何工作的呢? 
    先从ShiroFilter入手,因为它是总的拦截器,看看其中的doFilterInternal(...)方法:

    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)            throws ServletException, IOException {        Throwable t = null;        try {            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);            final Subject subject = createSubject(request, response);            //noinspection unchecked            subject.execute(new Callable() {                public Object call() throws Exception {                    // 其实需要关心的就在这里                    // touch一下session                    updateSessionLastAccessTime(request, response);                    // 执行Filter链                    executeChain(request, response, chain);                    return null;                }            });        } catch (ExecutionException ex) {            t = ex.getCause();        } catch (Throwable throwable) {            t = throwable;        }        if (t != null) {            if (t instanceof ServletException) {                throw (ServletException) t;            }            if (t instanceof IOException) {                throw (IOException) t;            }            //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:            String msg = "Filtered request failed.";            throw new ServletException(msg, t);        }    }

    跟进executeChain(...)方法:

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

    如何得到FilterChain的呢?如果你认真的看到这里,那么你应该不难想到其中肯定利用了刚才注册的ChainResolver

    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;        }        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;    }

    猜对了~并且也验证了当resolver.getChain(...)返回null时,直接使用originChain了。然后执行返回的FilterChaindoFilter(...)方法。这个过程我们再脱离代码来分析一下:当我们从浏览器发出一个请求,究竟发生了什么? 

    这里只站在Filter的层面来分析。服务器启动后,读取web.xml中的filterfilter-mapping节点后组成FilterChain,对请求进行拦截。拦截的顺序按照filter节点的定义顺序,Shiro利用ShiroFilter来充当一个总的拦截器来分发所有需要被Shiro拦截的请求,所以我们看到在Shiro中我们还可以自定义拦截器。ShiroFilter根据它在拦截器中的位置,只要执行到了那么就会暂时中断原FilterChain的执行,先执行Shiro中定义的Filter,最后再执行原FilterChian。可以打个比方,比如说本来有一条铁链,一直蚂蚁从铁链的开端往末端爬,其中某一环叫ShiroFilter,那么当蚂蚁爬到ShiroFilter这一环时,将铁链打断,并且接上另一端铁链(Shiro中自定义的Filter),这样就构成了一条新的铁链。然后蚂蚁继续爬行(后续的执行过程)。

    到这里,我们已经根据请求路径找到了一条Filter链(originChain + shiroChain),之后就是对链上的FilterdoFilter,其中关于如何 

    就是filter后配置的[]部分是如何生效的,我们可以看PathMatchingFilter中的Prehandle(...)方法:

    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {        if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {            if (log.isTraceEnabled()) {                log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");            }            return true;        }        // appliedPaths中保存了该filter中能拦截的路径和该路径配置的key-value对,比如{key="/admin/**", value="[admin]"}        for (String path : this.appliedPaths.keySet()) {            // 首先是匹配路径            if (pathsMatch(path, request)) {                log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);                // 然后开始验证“[]”中的字符串                Object config = this.appliedPaths.get(path);                return isFilterChainContinued(request, response, path, config);            }        }        //no path matched, allow the request to go through:        return true;    }

    下面跟踪isFilterChainContinued(...)

    private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,                                           String path, Object pathConfig) throws Exception {        if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2            if (log.isTraceEnabled()) {                 // log            }            return onPreHandle(request, response, pathConfig);        }        if (log.isTraceEnabled()) {            // log        }        return true;    }

    基本也就是交给onPreHandle(...)来处理,所以一般需要验证”[]“中字符串的filter都会扩展这个方法,比如AccessControlFilter

    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);    }

    RolesAuthorizationFilter中:

    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {        Subject subject = getSubject(request, response);        String[] rolesArray = (String[]) mappedValue;        if (rolesArray == null || rolesArray.length == 0) {            //no roles specified, so nothing to check - allow access.            return true;        }        Set
    roles = CollectionUtils.asSet(rolesArray); return subject.hasAllRoles(roles); }

    最后附上默认注册的filters

    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);}

     

转载于:https://my.oschina.net/u/2819035/blog/736724

你可能感兴趣的文章
求助radius使用本地系统用户认证***问题
查看>>
android Handler详细使用方法实例
查看>>
Mysql组合索引顺序问题
查看>>
我的友情链接
查看>>
aftersave 生命周期函数
查看>>
js弹窗输入
查看>>
tomcat 远程监控配置(JConsole)
查看>>
网上很盛传的网络通信的爱情
查看>>
android 反编译
查看>>
常用正则表达式(一)
查看>>
varnish 简单应用
查看>>
C++中 简单查看临时对象,局部对象的生命周期,及拷贝构造函数(测试代码)...
查看>>
u-boot移植
查看>>
DHCP服务器在企业网络中的应用
查看>>
ansible主机key检查
查看>>
live555学习笔记整理,转载
查看>>
cocos2d-x物理引擎的实现
查看>>
下载编译webrtc for ios
查看>>
localhost与127.0.0.1区别
查看>>
Python异常处理
查看>>