Shiro源码研究之处理一次完整的请求

经过上一篇博客里的讨论,我们知道了最终被并入到Servlet的FilterChain中的Filter实例为ShiroFilterFactoryBean.SpringShiroFilter类型。而这篇博客就让我们来看看Shiro是如何借助这个Filter来完成权限校验这个庞大功能的?

1. ShiroFilterFactoryBean.SpringShiroFilter

现在让我们来看看ShiroFilterFactoryBean.SpringShiroFilter 类的类定义。

// ShiroFilterFactoryBean.SpringShiroFilter
private static final class SpringShiroFilter extends AbstractShiroFilter {

    protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
        super();
        if (webSecurityManager == null) {
            throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
        }
        setSecurityManager(webSecurityManager);
        if (resolver != null) {
            setFilterChainResolver(resolver);
        }
    }
}

代码很简单,只有securityManagerfilterChainResolver字段的设置,因此主要逻辑肯定就在其基类AbstractShiroFilter了。

2. AbstractShiroFilter.doFilterInternal

作为一个Filter,其核心方法当然就是doFilter了。

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
        throws ServletException, IOException {

    Throwable t = null;

    try {
        // 将接收到的servletRequest,servletResponse使用Wrapper模式封装为自定义的ShiroHttpServletRequest, ShiroHttpServletResponse
        final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
        final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

        // 构建一个Subject
        // 这里的subject的实际类型是WebDelegatingSubject
        // 详细解释参见本人的另外一篇博客: http://blog.csdn.net/lqzkcx3/article/details/78801403
        final Subject subject = createSubject(request, response);

        //noinspection unchecked
        // 这个execute方法实际被定义在DelegatingSubject类中
        // 这个execute将方法的调用者subject存入到当前Thread的线程本地存储中,也就是将该Subject实例与当前Thread进行绑定。具体细节参见本人的另一篇博客http://blog.csdn.net/lqzkcx3/article/details/78801403的第6节。 
        // 那样在我们调用`SecurityUtils.getSubject()`就能直接获取到该Subject实例了。
        subject.execute(new Callable() {
            public Object call() throws Exception {
                // 更新最后读取session的时间
                updateSessionLastAccessTime(request, response);
                // 核心,下文进行讲解
                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);
    }
}

3. AbstractShiroFilter.executeChain方法

处理请求时的核心方法

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

4. AbstractShiroFilter.getExecutionChain方法

// AbstractShiroFilter.getExecutionChain
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
    FilterChain chain = origChain;

    //上一篇文章提到了,filterChainResolver字段被硬编码为PathMatchingFilterChainResolver;当然我们是可以自定义,不过很少有这个必要。
    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;
}

5. PathMatchingFilterChainResolver.getChain方法

// PathMatchingFilterChainResolver.getChain
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
    // DefaultFilterChainManager
    FilterChainManager filterChainManager = getFilterChainManager();
    if (!filterChainManager.hasChains()) {
        return null;
    }

    // 以 http://www.somehost.com/myapp/my/url.jsp举例的话;这里返回的requestURI, 其值为 /my/url.jsp
    String requestURI = getPathWithinApplication(request);

    //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
    //as the chain name for the FilterChainManager's requirements

    /* 这里我们以上一篇博客的例子里的配置来进行讲解
        /login.jsp = authc
        /logout = logout
        /unauthorized.jsp = anon
        /main**           = authc
        /admin/list**     = authc,perms[admin:manage]
        /user/info-anon** = anon
        /user/info**      = authc           
        /** = anon

        每次迭代的pathPattern就是上面的"/login.jsp", "/logout","/unauthorized.jsp"等等
    */  
    // 一个pathPattern可能被配置多个Filter, 而shiro采用了继承自List<Filter>的NamedFilterList接口来存储这些Filter
    // 所以DefaultFilterChainManager类中有一个Map<String, NamedFilterList>类型的filterChains字段, 其中存放的正式上面用户配置的关系
    // NamedFilterList接口到1.3.2为止, 唯一的实现依然是SimpleNamedFilterList
    // DefaultFilterChainManager类中另外一个Map<String, Filter>类型的字段 filters ,让我们看看其内部的存储:
    //     {anon=anon, authc=authc, authcBasic=authcBasic, logout=logout, noSessionCreation=noSessionCreation, perms=perms, port=port, rest=rest, roles=roles, ssl=ssl, user=user, xxxxModuleAuthc=xxxxModuleAuthc, upmsFormAuthenticationFilter=upmsFormAuthenticationFilter, upmsSessionForceLogout=upmsSessionForceLogout}
    //  所以它里面存储的是默认的Filter + 我们自定义注册的Filter

    // 筛选出与当前请求链接匹配的FilterChain
    for (String pathPattern : filterChainManager.getChainNames()) {

        // If the path does match, then pass on to the subclass implementation for specific checks:
        // 这里默认的匹配模式为org.apache.shiro.util.AntPathMatcher。
        if (pathMatches(pathPattern, requestURI)) {
            // 一旦匹配, 则对Servlet的FilterChain进行代理。保证Shiro自定义的Filter执行完毕之后,再执行Servlet的Filter。
            // 下文进行细节讲解
            return filterChainManager.proxy(originalChain, pathPattern);
        }
    }

    return null;
}

6. DefaultFilterChainManager.proxy方法

public FilterChain proxy(FilterChain original, String chainName) {
    // 从上一小节里讲解的filterChains字段中取出对应的NamedFilterList
    NamedFilterList configured = getChain(chainName);
    if (configured == null) {
        String msg = "There is no configured chain under the name/key [" + chainName + "].";
        throw new IllegalArgumentException(msg);
    }

    // proxy实现是方法的实现如下 :
    //    return new ProxiedFilterChain(orig, this);
    return configured.proxy(original);
}

7. ProxiedFilterChain

ProxiedFilterChain类 实现了Servlet规范中的FilterChain接口,

// ProxiedFilterChain.doFilter
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
    // Shiro自身的Filter执行完毕之后; 开始执行Servlet源生的Filter
    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 + "]");
        }

        // 执行Shiro自身定义的Filter。
        // 关于filters字段在上一小节中已经谈及过;更具体的还请参见下一篇博客。Shiro针对Filter的继承链还是比较深的,值得专门写一篇。
        this.filters.get(this.index++).doFilter(request, response, this);
    }
}

8. 总结

  1. 相比较本人之前对Filter的扩展,一直考虑的都是向FilterChain中插入自定义Filter; Shiro则直接来了个更狠的,直接将整个FilterChain代理掉,先执行完我的FilterChain,才考虑Servlet的。
  2. Shiro 对Servlet 容器的FilterChain 进行了代理,即ShiroFilter 在继续Servlet 容器的Filter链的执行之前,通过ProxiedFilterChain`对Servlet 容器的FilterChain 进行了代理;即先走Shiro 自己的Filter 体系,然后才会委托给Servlet 容器的FilterChain 进行Servlet 容器级别的Filter链执行; 《跟开涛学Shiro》P77
  3. 每次请求的到来,Shiro都会从我们配置到org.apache.shiro.spring.web.ShiroFilterFactoryBeanfilterChainDefinitions中挑选一个匹配过滤链(多个匹配也只会选择第一个匹配的),Shiro会执行这个链条,最后采取执行其他Servlet Filter。
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
根据提供的引用内容,可以得知在使用shiro进行跨域请求时,会出现请求两次的问题。这是因为后台采用了token检验机制,前台发送请求必须将token放到request header中,而请求头中携带自定义参数,浏览器就认为请求是复杂跨域请求,所以浏览器在真正请求之前会发送一次预检请求,检测服务器是否支持真实请求进行跨域访问。 解决方案如下: 1.在后台代码中添加如下配置,允许跨域请求: ```java // 允许跨域请求 response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "x-requested-with,Authorization"); ``` 2.在shiro配置文件中添加如下配置,允许OPTIONS请求通过: ```xml <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login"/> <property name="successUrl" value="/index"/> <property name="unauthorizedUrl" value="/unauthorized"/> <property name="filters"> <util:map> <entry key="authc"> <bean class="org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter"/> </entry> </util:map> </property> <property name="filterChainDefinitions"> <value> /login = anon /logout = logout /** = authc </value> </property> </bean> ``` 3.在前端代码中添加如下配置,允许携带自定义参数: ```javascript axios.defaults.headers.common['Authorization'] = 'Bearer ' + token; axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; axios.defaults.withCredentials = true; ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值