shiroFilter生命周期

本文详细剖析了Shiro权限认证的整个流程,包括ShiroFilter的初始化、doFilter方法的具体执行过程以及销毁方法等内容。同时介绍了如何通过配置实现不同的权限控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基本说明

filter执行的是代理bean的id为shiroFilter相应的方法

接下来看看 shiroFilter 是什么

再来分析shiroFilter 这个实现了 javax.servlet.Filter 的生命周期

spring 配置信息

 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
        <property name="loginUrl" value="/login.action" />
        <!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 -->
        <property name="successUrl" value="/index.action" />
        <!-- 通过unauthorizedUrl指定没有权限操作时跳转页面 -->
        <property name="unauthorizedUrl" value="/refuse.action" />

        <!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
        <property name="filterChainDefinitions">
            <value>
                <!-- 静态资源放行 -->
                /login/account = anon

                /user = perms["user:view"]
                <!--商品查询需要商品查询权限 ,取消url拦截配置,使用注解授权方式 -->
                <!-- /itemEdit.action = perms[item:edit] -->

                <!-- 请求 logout.action地址,shiro去清除session -->
                /logout = logout

                <!-- /** = authc 所有url都必须认证通过才可以访问 -->
                /** = authc

                <!-- 所有url都可以匿名访问 -->
                <!-- /** = anon -->
            </value>
        </property>
    </bean>

    <!-- securityManager安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 注入realm -->
        <property name="realm" ref="customRealm" />

        <property name="sessionManager" ref="sessionManager"/>

    </bean>

    <!-- 会话管理器 -->
    <bean id="sessionManager"
          class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- session的失效时长,单位毫秒 -->
        <property name="globalSessionTimeout" value="600000" />
        <!-- 删除失效的session -->
        <property name="deleteInvalidSessions" value="true" />
    </bean>

    <!-- realm -->
    <bean id="customRealm" class="org.apache.shiro.realm.text.IniRealm">
        <constructor-arg index="0" value="classpath:shiro.ini"/>
    </bean>

获取shiroFilter

ShiroFilterFactoryBean类图

ShiroFilterFactoryBean 实现了 FactoryBean ,那么实际上getBean的时候,返回的是getObject的内容而不是ShiroFilterFactoryBean

(如果不理解实现 FactoryBean 会返回 getObject 的同学, 期待接下来的spring源码分析,或者自行百度)

接下来分析 ShiroFilterFactoryBean 的getObject 返回的对象是什么,才能知道 init,doFilter,destory 的时候做了什么

ShiroFilterFactoryBean

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

级别2

instance = createInstance();

  1. 验证securityManager不为空,验证 securityManager 必须是 WebSecurityManager
  2. 创建 过滤链管理器(责任链模式),将shiro默认的验证类型和用户自己定义的过滤器添加到管理器
  3. 返回一个 SpringShiroFilter 对象

ShiroFilterFactoryBean

 protected AbstractShiroFilter createInstance() throws Exception {

            log.debug("Creating Shiro Filter instance.");

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

            //Expose the constructed FilterChainManager by first wrapping it in a
            // FilterChainResolver implementation. The AbstractShiroFilter implementations
            // do not know about FilterChainManagers - only resolvers:
            PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
            chainResolver.setFilterChainManager(manager);

            //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
            //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
            //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
            //injection of the SecurityManager and FilterChainResolver:
            return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
     }

所以在web中配置的 shiroFilter 实际上是一个 SpringShiroFilter 对象,我们要分析的shiro在web中的使用,也就是分析 SpringShiroFilter 的生命周期

shiroFilter

类图

init

  1. 设置filterConfig和servletContext
  2. 确保 this.securityManager 已被初始化

SpringShiroFilter

    public void setFilterConfig(FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
        setServletContext(filterConfig.getServletContext());
    }

    public final void init(FilterConfig filterConfig) throws ServletException {
        setFilterConfig(filterConfig);
        try {
            onFilterConfigSet();
        } catch (Exception e) {
            if (e instanceof ServletException) {
                throw (ServletException) e;
            } else {
                if (log.isErrorEnabled()) {
                    log.error("Unable to start Filter: [" + e.getMessage() + "].", e);
                }
                throw new ServletException(e);
            }
        }
    }

级别2

onFilterConfigSet();

  1. 当 init-param 的 staticSecurityManagerEnabled 不为空就设置 this.staticSecurityManagerEnabled
  2. init是空的,留给子类去实现,当前情况下没有子类去实现,就是空的
  3. 当 securityManager 为空的时候,创建默认的securityManager
  4. 判断 this.staticSecurityManagerEnabled 为true,就设置 SecurityUtils.securityManager

AbstractShiroFilter

// 当 init-param 的 staticSecurityManagerEnabled 不为空就设置 this.staticSecurityManagerEnabled
private void applyStaticSecurityManagerEnabledConfig() {
        String value = getInitParam(STATIC_INIT_PARAM_NAME);
        if (value != null) {
            Boolean b = Boolean.valueOf(value);
            if (b != null) {
                setStaticSecurityManagerEnabled(b);
            }
        }
    }
//当 securityManager 为空的时候,创建默认的securityManager
  private void ensureSecurityManager() {
        WebSecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            log.info("No SecurityManager configured.  Creating default.");
            securityManager = createDefaultSecurityManager();
            setSecurityManager(securityManager);
        }
    }

 protected final void onFilterConfigSet() throws Exception {
        //added in 1.2 for SHIRO-287:
        applyStaticSecurityManagerEnabledConfig();

        init();
        ensureSecurityManager();
        //added in 1.2 for SHIRO-287:
        if (isStaticSecurityManagerEnabled()) {
            SecurityUtils.setSecurityManager(getSecurityManager());
        }
    }

doFilter

父类 OncePerRequestFilter 实现了 doFilter

  1. 判断是否有 filterName().FILTERED,不为空就 执行下一个过滤器
  2. 判断 没有启用过滤器 或者 request 没有过滤器就执行下一个过滤器
  3. 设置 filterName().FILTERED = true, 执行当前过滤器

OncePerRequestFilter

 public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
            filterChain.doFilter(request, response);
        } else //noinspection deprecation
            if (/* added in 1.2: */ !isEnabled(request, response) ||
                /* retain backwards compatibility: */ shouldNotFilter(request) ) {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                    getName());
            filterChain.doFilter(request, response);
        } else {
            // Do invoke this filter...
            log.trace("Filter '{}' not yet executed.  Executing now.", getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                doFilterInternal(request, response, filterChain);
            } finally {
                // Once the request has finished, we're done and we don't
                // need to mark as 'already filtered' any more.
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }

级别2

doFilterInternal(request, response, filterChain);

  1. 用 ShiroHttpServletRequest 包装servletRequest,ShiroHttpServletResponse 包装servletResponse (这是一个典型的装饰模式)
  2. 创建当前用户 Subject
  3. 当前用户执行访问目标操作

在这里创建用户的部分可以参考shiro 基础章节, 就是从 this.securityManager.createSubject(this.subjectContext);

访问目标 subject.execute 就是接下来进行重点讲解的了

AbstractShiroFilter

 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 {
                    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-1

subject.execute

这里就是一个很典型的代理模式 ,用 SubjectCallable 代理了原来的 callable.

然后再执行真正的call

public SubjectCallable(Subject subject, Callable<V> delegate) {
    // 设置TheadState
        this(new SubjectThreadState(subject), delegate);
    }

 public <V> Callable<V> associateWith(Callable<V> callable) {
    //返回代理类
        return new SubjectCallable<V>(this, callable);
    }

public <V> V execute(Callable<V> callable) throws ExecutionException {
        Callable<V> associated = associateWith(callable);
        try {
            // 代理类 call
            return associated.call();
        } catch (Throwable t) {
            throw new ExecutionException(t);
        }
    }

级别3-2

updateSessionLastAccessTime(request, response);

通过名字就可以判断出来, 更新session的最后访问时间。

但是访问的时候带sessionId会报异常,不过没什么影响,可以忽略,或者关闭这个日志

http://localhost:8080/login.action;JSESSIONID=2bf853ec-f439-4981-8c01-7bfa7456c878

 org.apache.shiro.session.UnknownSessionException: There is no session with id [b81ed452-882c-4cd1-a8e8-b1bfd7e3cee8]
    at org.apache.shiro.session.mgt.eis.AbstractSessionDAO.readSession(AbstractSessionDAO.java:170)

级别3-3

executeChain(request, response, chain);

这段内容也比较简单, 就是用当前的 FilterChainResolver 去获取 FilterChain ,

然后返回去执行 chain.doFilter(request, response);

这一段的重点内容也就在于 resolver.getChain(request, response, origChain);

这里会具体进行角色权限的判断,判断用户是不是有url的访问权限

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

executeChain步骤(一)

级别1

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

级别2

FilterChain resolved = resolver.getChain(request, response, origChain);

获得当前链的操作 做了一下几个步骤

  1. 获取 FilterChainManager (在ShiroFilterFactoryBean的getObject下面的createInstance创建的)
  2. 获取 requestURI
  3. 遍历配置的 filterChainDefinitions 属性的key。 判断key是否能匹配requestURI。 如果能匹配 就返回value对应的Filter
    接下来解析如何通过value获取对应的filter

如果有更多的兴趣: 可以去查看 如何匹配请求uri的filter

PathMatchingFilterChainResolver

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

        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
        for (String pathPattern : filterChainManager.getChainNames()) {

            // If the path does match, then pass on to the subclass implementation for specific checks:
            if (pathMatches(pathPattern, requestURI)) {
                if (log.isTraceEnabled()) {
                    log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " +
                            "Utilizing corresponding filter chain...");
                }
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }

        return null;
    }

级别3

return filterChainManager.proxy(originalChain, pathPattern);

这里就是通过 配置的key获取对应的Filter

例如: /* = authc 就是通过/* 获取shiro内置权限认证对象: NamedFilterList

DefaultFilterChainManager

    public NamedFilterList getChain(String chainName) {
            return this.filterChains.get(chainName);
     }

    public FilterChain proxy(FilterChain original, String chainName) {
        NamedFilterList configured = getChain(chainName);
        if (configured == null) {
            String msg = "There is no configured chain under the name/key [" + chainName + "].";
            throw new IllegalArgumentException(msg);
        }
        return configured.proxy(original);
    }

级别4

return configured.proxy(original);

这里可以看到,是通过 NamedFilterList 和 FilterChain 创建了一个代理对象 ProxiedFilterChain

SimpleNamedFilterList

    public FilterChain proxy(FilterChain orig) {
        return new ProxiedFilterChain(orig, this);
    }

executeChain步骤(二)

chain.doFilter(request, response);

通过 executeChain步骤(一) 就已经获取到了 FilterChain 对象。
返回的是一个 ProxiedFilterChain 对象。
这一步实际上跟进的就是 ProxiedFilterChain.doFilter

到这里为止,其实就已经分析完成了。因为类似: /** = authc 可以有各种各样的配置。
在最下面的附注会附上 perms 和 authc 的doFilter 分析

ProxiedFilterChain

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

destory

卸载的时候什么都不做

AbstractFilter

   public void destroy() {
    }

附注

当前用户调用代理

做的事情非常简单,就是在执行目标前后进行 threadState 的设置和还原

其实就是设置当前线程的 Subject 和 SecurityManager

SubjectCallable

    protected V doCall(Callable<V> target) throws Exception {
        return target.call();
    }
    public V call() throws Exception {
        try {
            threadState.bind();
            return doCall(this.callable);
        } finally {
            threadState.restore();
        }
    }

路径匹配

默认过滤器

shiro内置权限认证对象

认证过滤器

authc对应的filter: FormAuthenticationFilter

FormAuthenticationFilter类图

FormAuthenticationFilter 继承 OncePerRequestFilter 那么 doFilter 实际上就是执行 doFilterInternal

    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
                //TODO 省略
                doFilterInternal(httpRequest, httpResponse, filterChain);
    }

级别2

doFilterInternal(httpRequest, httpResponse, filterChain);

这里的代码整体流程也非常清晰
1. 预处理请求 (进行url匹配,匹配成功后进行认证。 认证成功后 continueChain=true)
2. 通过访问控制器判断是否要执行调用链,
3. 清理调用链 (在这里什么都没做)

那么这里就可以确定最最重要的部分 perHandler

AdviceFilter

    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        Exception exception = null;

        try {

            boolean continueChain = preHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");
            }

            if (continueChain) {
                executeChain(request, response, chain);
            }

            postHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Successfully invoked postHandle method");
            }

        } catch (Exception e) {
            exception = e;
        } finally {
            cleanup(request, response, exception);
        }
    }

级别3

boolean continueChain = preHandle(request, response);

  1. 这里判断是否存在当前匹配器 ,例如 全局都没有配置 /user = authc, 那么就表示没有authc匹配器
  2. 遍历匹配器的key,用请求的uri和配置的匹配器进行匹配
  3. 进入下一阶段获取是否可以继续执行filterChain

PathMatchingFilter

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

        for (String path : this.appliedPaths.keySet()) {
            // If the path does match, then pass on to the subclass implementation for specific checks
            //(first match 'wins'):

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

级别4

return isFilterChainContinued(request, response, path, config);

onPreHandle 由 访问控制器分析 实现.

PathMatchingFilter

 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.trace("Filter '{}' is enabled for the current request under path '{}' with config [{}].  " +
                        "Delegating to subclass implementation for 'onPreHandle' check.",
                        new Object[]{getName(), path, pathConfig});
            }
            //The filter is enabled for this specific request, so delegate to subclass implementations
            //so they can decide if the request should continue through the chain or not:
            return onPreHandle(request, response, pathConfig);
        }

        if (log.isTraceEnabled()) {
            log.trace("Filter '{}' is disabled for the current request under path '{}' with config [{}].  " +
                    "The next element in the FilterChain will be called immediately.",
                    new Object[]{getName(), path, pathConfig});
        }
        //This filter is disabled for this specific request,
        //return 'true' immediately to indicate that the filter will not process the request
        //and let the request/response to continue through the filter chain:
        return true;
    }

访问控制器

FormAuthenticationFilter类图

  1. 判断是否允许访问, 允许访问就返回true
  2. 拒绝访问(无论如何都返回false)

接下来就要进行两部分分析了 isAccessAllowed 和 onAccessDenied
AccessControlFilter

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

isAccessAllowed

  1. 判断subject 是否已经认证
  2. 判断是不是loginUrl
  3. 判断是否有授权
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return super.isAccessAllowed(request, response, mappedValue) ||
                (!isLoginRequest(request, response) && isPermissive(mappedValue));
    }

    //判断subject 是否已经认证
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        Subject subject = getSubject(request, response);
        return subject.isAuthenticated();
    }
    //判断是不是loginUrl
    protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
            return pathsMatch(getLoginUrl(), request);
        }

    protected boolean isPermissive(Object mappedValue) {
             if(mappedValue != null) {
                 String[] values = (String[]) mappedValue;
                 return Arrays.binarySearch(values, PERMISSIVE) >= 0;
             }
             return false;
    }

    // 是否有访问权限,
    protected boolean isPermissive(Object mappedValue) {
            if(mappedValue != null) {
                String[] values = (String[]) mappedValue;
                return Arrays.binarySearch(values, PERMISSIVE) >= 0;
            }
            return false;
    }

onAccessDenied

调用子类实现,发现子类无论如何都会返回false

也就是说 当isAccessAllowed的结果为false(不允许访问的时候)
1. 当前用户的凭证信息为空的时候, 跳转到登录页
2. 判断是否已经配置 unauthorizedUrl (未授权跳转到的url),如果已经配置就跳转到 unauthorizedUrl, 如果没有配置没权限页面,返回错误码401 (HttpServletResponse.SC_UNAUTHORIZED)

AccessControlFilter


    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return onAccessDenied(request, response);
    }

AuthorizationFilter

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {

        Subject subject = getSubject(request, response);
        // If the subject isn't identified, redirect to login URL
        if (subject.getPrincipal() == null) {
            saveRequestAndRedirectToLogin(request, response);
        } else {
            // If subject is known but not authorized, redirect to the unauthorized URL if there is one
            // If no unauthorized URL is specified, just return an unauthorized HTTP status code
            String unauthorizedUrl = getUnauthorizedUrl();
            //SHIRO-142 - ensure that redirect _or_ error code occurs - both cannot happen due to response commit:
            if (StringUtils.hasText(unauthorizedUrl)) {
                WebUtils.issueRedirect(request, response, unauthorizedUrl);
            } else {
                WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
        }
        return false;
    }

权限过滤器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值