Shiro使用和源码分析---3

来源:互联网 发布:微信推广源码 编辑:程序博客网 时间:2024/06/06 20:47

Shiro使用和源码分析—3

接着上一章的内容,Shiro框架最后会调用过滤器的doFilter函数。
为了方便叙述,继续引用一些applicationContext.xml中的配置。

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">        <property name="securityManager" ref="securityManager"/>        <property name="filters">            <util:map>                <entry key="authc" value-ref="customAuthenticationFilter"/>            </util:map>        </property>        <property name="filterChainDefinitions">            <value>                  /test = anon                /** = authc            </value>        </property>    </bean>

为了方便分析,首先自定义一个customAuthenticationFilter继承自FormAuthenticationFilter,如下所示

customAuthenticationFilter

public class CustomAuthenticationFilter extends FormAuthenticationFilter {    private Map<String, String> loginUrlByUserAgent = new HashMap<String, String>();    private String customAdminLogin;    public void setLoginUrls(final Map<String, String> loginUrlByUserAgent) {        this.loginUrlByUserAgent = loginUrlByUserAgent;    }    protected void redirectToLogin(final ServletRequest request, final ServletResponse response) throws IOException {        final String loginUrl = getLoginUrl(request);        WebUtils.issueRedirect(request, response, loginUrl);    }    private String getLoginUrl(final ServletRequest request) {        String requestUrl = ((HttpServletRequest)request).getRequestURL().toString();        String context = request.getServletContext().getContextPath();        if(requestUrl.equals("") ){//这里没有具体的定义,实际程序可以根据情况改写            return customAdminLogin;        }else{            return getLoginUrl();        }    }    public void setCustomAdminLogin(String customAdminLogin) {        this.customAdminLogin = customAdminLogin;    }}

在applicationContext.xml配置这个过滤器,

    <bean id="customAuthenticationFilter"           class="com.shiro.CustomAuthenticationFilter">          <property name="usernameParam" value="username"/>          <property name="passwordParam" value="password"/>          <property name="customlogin" value="/customlogin.jsp"/>        <property name="customAdminLogin" value="/customAdminLogin.jsp"></property>    </bean>

下面先把CustomAuthenticationFilter 一直往上继承的类显示下来

public class CustomAuthenticationFilter extends FormAuthenticationFilterpublic class FormAuthenticationFilter extends AuthenticatingFilterpublic abstract class AuthenticatingFilter extends AuthenticationFilterpublic abstract class AuthenticationFilter extends AccessControlFilterpublic abstract class AccessControlFilter extends PathMatchingFilterpublic abstract class PathMatchingFilter extends AdviceFilter implements PathConfigProcessorpublic abstract class AdviceFilter extends OncePerRequestFilterpublic abstract class OncePerRequestFilter extends NameableFilterpublic abstract class NameableFilter extends AbstractFilter implements Nameablepublic abstract class AbstractFilter extends ServletContextSupport implements Filterpublic class ServletContextSupport

上一章简单介绍了各个类的作用,这里就不再叙述了。首先看Filter接口的init函数,定义在AbstractFilter中,如下所示

init

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

setFilterConfig用于设置过滤器配置和ServletContext环境。

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

onFilterConfigSet用于设定一些回调函数,目前定义在AbstractFilter中,为空函数。
看完了init函数,doFilter函数的实现在哪呢?在OncePerRequestFilter中。

doFilter

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

和前面一章一样啦,这里就简单copy一下。该函数通过request中的alreadyFilteredAttributeName属性判断该request是否已经被该filter过滤过,如果已经被过滤了,就跳过该过滤器。另外,过滤器必须设置为enable即开启,如果关闭了该过滤器,isEnabled函数判断enable为false,则也跳过该过滤器。其他情况下,则进入该filter过滤器。首先设置request属性alreadyFilteredAttributeName为true,表是已经通过该过滤器了,下次不经过了,然后调用doFilterInternal。

doFilterInternal函数就和上一章不一样了,实现在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);        }    }

doFilterInternal中一共有四个函数会执行,分别是执行过滤前的preHandle、过滤中的executeChain、过滤后的postHandle函数,最后处理异常的cleanup函数。executeChain就是简单通过该过滤器,postHandle为空函数。下面重点分析preHandle和cleanup函数。

preHandle

preHandle定义在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 (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);            }        }        return true;    }

上一章说过,对于配置文件中的”/test = anon,/**”, authcappliedPaths保存了一个Map,其中的key分别是”/test”和”/**”,config就为null啦。再回头看FormAuthenticationFilter中的setLoginUrl函数,

    public void setLoginUrl(String loginUrl) {        String previous = getLoginUrl();        if (previous != null) {            this.appliedPaths.remove(previous);        }        super.setLoginUrl(loginUrl);        if (log.isTraceEnabled()) {            log.trace("Adding login url to applied paths.");        }        this.appliedPaths.put(getLoginUrl(), null);    }

结合applicationContext.xml的设置,因此appliedPaths也保存了loginUrl=”/customlogin.jsp”。
继续往下看preHandle函数,接着从appliedPaths依次取出url,调用pathsMatch进行匹配。注意注意这里依次取出的是appliedPaths这个Map的key,并不是value。昨天看源码看到这里的时候想了半天。pathsMatch如下所示

    protected boolean pathsMatch(String path, ServletRequest request) {        String requestURI = getPathWithinApplication(request);        log.trace("Attempting to match pattern '{}' with current requestURI '{}'...", path, requestURI);        return pathsMatch(path, requestURI);    }

getPathWithinApplication从request中获取相对url,然后调用另一个多态的pathsMatch继续匹配。如下所示

    protected boolean pathsMatch(String pattern, String path) {        return pathMatcher.matches(pattern, path);    }

matches函数主要是一些字符串的处理,这里就不分析了。回到preHandle函数,如果匹配成功,则执行isFilterChainContinued函数,如下所示

    private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,                                           String path, Object pathConfig) throws Exception {        if (isEnabled(request, response, path, pathConfig)) {            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});            }            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});        }        return true;    }

isEnabled就是一个开关啦,这里返回true,不管它。这里主要是继续执行了onPreHandle函数。

onPreHandle

onPreHandle的定义在AccessControlFilter中,如下所示

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

只要isAccessAllowed或者onAccessDenied有一个为真,就返回true,继续执行。isAccessAllowed定义在AuthenticatingFilter中

    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {        return super.isAccessAllowed(request, response, mappedValue) ||                (!isLoginRequest(request, response) && isPermissive(mappedValue));    }

super.isAccessAllowed定义在AuthenticationFilter中

    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {        Subject subject = getSubject(request, response);        return subject.isAuthenticated();    }

getSubject获得Subject,用于判断是否已经登录。如果已经登录了,则isAuthenticated返回true。一直返回到最上层,通过该过滤器。如果未登陆,则调用isLoginRequest和isPermissive。
isLoginRequest定义在AccessControlFilter中,如下

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

结合前面的isLoginRequest函数,这里的意思是说,如果用户请求的不是登录页面,但是在定义该过滤器时,使用了PERMISSIVE=”permissive”参数,则也可以通过该过滤器。

回到onPreHandle函数,onAccessDenied定义在FormAuthenticationFilter中

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {        if (isLoginRequest(request, response)) {            if (isLoginSubmission(request, response)) {                if (log.isTraceEnabled()) {                    log.trace("Login submission detected.  Attempting to execute login.");                }                return executeLogin(request, response);            } else {                if (log.isTraceEnabled()) {                    log.trace("Login page view.");                }                //allow them to see the login page ;)                return true;            }        } else {            if (log.isTraceEnabled()) {                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +                        "Authentication url [" + getLoginUrl() + "]");            }            saveRequestAndRedirectToLogin(request, response);            return false;        }    }

isLoginSubmission用于判断客户端请求是不是一个HTTP的post请求,结合isLoginRequest说明shiro判断客户端的登录请求必须是post的。

    protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {        return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);    }

如果条件都满足,就执行登录函数executeLogin,定义在AuthenticatingFilter中,代码如下

    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {        AuthenticationToken token = createToken(request, response);        if (token == null) {            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +                    "must be created in order to execute a login attempt.";            throw new IllegalStateException(msg);        }        try {            Subject subject = getSubject(request, response);            subject.login(token);            return onLoginSuccess(token, subject, request, response);        } catch (AuthenticationException e) {            return onLoginFailure(token, e, request, response);        }    }

这里就执行了Subject那方面的源码了,AuthenticationToken包含了用户名和密码的信息,然后调用Subject的Login函数。然后就执行login函数,如果没有抛出异常就调用onLoginSuccess函数。onLoginSuccess定义在FormAuthenticationFilter中,代码如下

    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,                                     ServletRequest request, ServletResponse response) throws Exception {        issueSuccessRedirect(request, response);        return false;    }

issueSuccessRedirect就是设置ServletResponse准备返回了,因此这里返回false,不会继续执行后面的过滤器了。issueSuccessRedirect定义在AuthenticationFilter中,代码如下

    protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {        WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());    }

这里就不继续往下看了,简单来说就是返回getSuccessUrl指向的url。
再来看onLoginFailure函数,定义在FormAuthenticationFilter中,代码如下

    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,                                     ServletRequest request, ServletResponse response) {        setFailureAttribute(request, e);        return true;    }

这里就是设置request的异常信息,交给后面cleanup函数处理。

回到onAccessDenied函数,如果不是一个登录请求,就执行saveRequestAndRedirectToLogin函数,代码如下

    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {        saveRequest(request);        redirectToLogin(request, response);    }

saveRequest将该request保存在session中,代码如下。

    protected void saveRequest(ServletRequest request) {        WebUtils.saveRequest(request);    }

下面就不细看了。redirectToLogin定义在AccessControlFilter中,代码如下

    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {        String loginUrl = getLoginUrl();        WebUtils.issueRedirect(request, response, loginUrl);    }

这里就是返回到设置的登录页面,在开头自定义的过滤器中就是重载了这个函数,在实际项目中,一般都会重载这个函数,比方说返回到指定的页面,例如登录失败了也会执行redirectToLogin函数,这时候一些错误信息的处理啊等等,都可以写在这个函数中。
这样就分析完了整个preHandle函数,回到doFilterInternal函数中,如果出现异常时执行cleanup函数。

cleanup

    protected void cleanup(ServletRequest request, ServletResponse response, Exception existing) throws ServletException, IOException {        if (existing instanceof UnauthenticatedException || (existing instanceof ServletException && existing.getCause() instanceof UnauthenticatedException))        {            try {                onAccessDenied(request, response);                existing = null;            } catch (Exception e) {                existing = e;            }        }        super.cleanup(request, response, existing);    }

这里还得继续执行onAccessDenied函数,前面已经分析过了,例如用户没有登录等原因则会继续返回到登录页面。另一个就是super.cleanup函数,定义在AdviceFilter中,

    protected void cleanup(ServletRequest request, ServletResponse response, Exception existing)            throws ServletException, IOException {        Exception exception = existing;        try {            afterCompletion(request, response, exception);            if (log.isTraceEnabled()) {                log.trace("Successfully invoked afterCompletion method.");            }        } catch (Exception e) {            if (exception == null) {                exception = e;            } else {                log.debug("afterCompletion implementation threw an exception.  This will be ignored to " +                        "allow the original source exception to be propagated.", e);            }        }        if (exception != null) {            if (exception instanceof ServletException) {                throw (ServletException) exception;            } else if (exception instanceof IOException) {                throw (IOException) exception;            } else {                if (log.isDebugEnabled()) {                    String msg = "Filter execution resulted in an unexpected Exception " +                            "(not IOException or ServletException as the Filter API recommends).  " +                            "Wrapping in ServletException and propagating.";                    log.debug(msg);                }                throw new ServletException(exception);            }        }    }

这里只是调用afterCompletion函数,默认为空函数。

到此,关于FormAuthenticationFilter过滤器的整个执行流程就分析完毕了。往后会继续分析一些Subject方面的源码了。

1 0
原创粉丝点击