Shiro__执行过滤器链过程&&补充了如何设置使用ajax异步提交等___源码跟踪

自定义过滤器

public class CustomFormAuthenticationFilter extends FormAuthenticationFilter{
	/**
	 * 认证成功后会调用次方法
	 */
	@Override
	protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
			ServletResponse response) throws Exception {
		// 获取登录信息
		String user = (String) subject.getPrincipal();
		System.out.println("登录账户:"+user);
		Session session = subject.getSession();
		session.setAttribute("msg", "自定义FormFilter传递的信息...");
		return super.onLoginSuccess(token, subject, request, response);
	}
}

public class RememberFormAuthenticationFilter extends FormAuthenticationFilter{
	
//    @Autowired
//	private SimpleCookie simpleCookie;
	
	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
		Subject subject = getSubject(request, response);
		Session session = subject.getSession();
		// 记住密码,没有登录isAuthenticated()肯定为false
		System.out.println("subject.isAuthenticated():"+subject.isAuthenticated());
		System.out.println("subject.isRemembered():"+subject.isRemembered());
		System.out.println("session.getAttribute(\"msg\"):"+session.getAttribute("msg"));
		if(!subject.isAuthenticated()
				&&subject.isRemembered()
				&&session.getAttribute("msg")==null){
			System.out.println("记住的用户是:"+subject.getPrincipal());
			session.setAttribute("msg", "remember中保存的信息");
		}
		return subject.isAuthenticated()||subject.isRemembered();
	}

}


FormAuthenticationFilter继承结构

继承结构中各类作用

类: 作用
AbstractFilter :    加载FilterConfig的相关信息
NameableFilter: 	定义每一个filter的名字
OncePerRequestFilter 	保证客户端请求后该filter的doFilter只会执行一次
AdviceFilter 	主要是对doFilterInternal做了更细致的处理
PathMatchingFilter 	主要是对preHandle做进一步细化控制
AccessControlFilter 	对onPreHandle方法做了进一步细化
AuthenticationFilter
AuthenticatingFilter 	用来做认证的Filter
FormAuthenticationFiltershiro 	用来具体的实现登录的Filter

这里要先说明,这样便于接下在的阅读,这个过滤器链,返回有false则被退出过滤器链,一直返回值为true则可以到达目标的controller控制层,也就是登陆的控制层,返回登陆界面,并不是登陆成功界面,只有未登录状态并且请求地址成功地址,请求方法为post,才可以在关键方法中返回为false,认证成功,并且成定向到成功地址

NameableFilter

public abstract class NameableFilter extends AbstractFilter implements Nameable {

    /**
     * The name of this filter, unique within an application.
     */
    private String name;

    /**
     * Returns the filter's name.
     * <p/>
     * Unless overridden by calling the {@link #setName(String) setName(String)} method, this value defaults to the
     * filter name as specified by the servlet container at start-up:
     * <pre>
     * this.name = {@link #getFilterConfig() getFilterConfig()}.{@link javax.servlet.FilterConfig#getFilterName() getName()};</pre>
     *
     * @return the filter name, or {@code null} if none available
     * @see javax.servlet.GenericServlet#getServletName()
     * @see javax.servlet.FilterConfig#getFilterName()
     */
    protected String getName() {
        if (this.name == null) {
            FilterConfig config = getFilterConfig();
            if (config != null) {
                this.name = config.getFilterName();
            }
        }

        return this.name;
    }

    /**
     * Sets the filter's name.
     * <p/>
     * Unless overridden by calling this method, this value defaults to the filter name as specified by the
     * servlet container at start-up:
     * <pre>
     * this.name = {@link #getFilterConfig() getFilterConfig()}.{@link javax.servlet.FilterConfig#getFilterName() getName()};</pre>
     *
     * @param name the name of the filter.
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Returns a StringBuilder instance with the {@link #getName() name}, or if the name is {@code null}, just the
     * {@code super.toStringBuilder()} instance.
     *
     * @return a StringBuilder instance to use for appending String data that will eventually be returned from a
     *         {@code toString()} invocation.
     */
    protected StringBuilder toStringBuilder() {
        String name = getName();
        if (name == null) {
            return super.toStringBuilder();
        } else {
            StringBuilder sb = new StringBuilder();
            sb.append(name);
            return sb;
        }
    }
}

NameableFilter有一个name属性,定义每一个filter的名字.在FilterChainManager 中会调用配置文件中的配置属性名字来为每一个filter命名以及为默认的filter命名,如authc.

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

当满足拦截条件的请求到来的时候执行的就是本方法中的OncePerRequestFilter 中的doFilter方法.
shiro是通过在request中设置一个该filter特定的属性值来保证该filter只会执行一次的,
就是enabled属性决定了是否执行本Filter:

doFilter的实质内容是在doFilterInternal方法中完成的。所以实质上是保证每一个filter的 doFilterInternal只会被执行一次。
例如在配置中配置路径 /login = authc,authc则只会执行authc中的doFilterInternal一次。doFilterInternal在shiro整个filter体系中的核心方法及实质入口。
这里的doFilterInternal实际上是个抽象方法,具体实现是在 AdviceFilter。

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

看到了preHandle和postHandle是不是很像SpringMVC中的拦截器

AdviceFilter对OncePerRequestFilter中的doFilterInternal做了一定程度的细化控制。

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

preHandle中,若请求的路径非该filter中配置的拦截路径,则直接返回true进行下一个filter。若包含在此filter路径中,则会在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.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;
    }


AccessControlFilter
onPreHandle方法在PathMatchingFilter就简单的返回了true,在AccessControlFilter 中更细化的实现了。

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

这里使用的是“||”“或”,所以调用完isAccessAllowed得到返回值后,需要调用onAccessDenied得到返回值,如果其中有true则进入下一个过滤器,为false则退出过滤器链进行相关操作。

isAccessAllowed方法和onAccessDenied方法达到控制效果
(抽象方法,需要子类实现)
isAccessAllowed和onAccessDenied方法会影响到onPreHandle方法,而onPreHandle方法会影响到preHandle方法,而preHandle方法会达到控制filter链是否执行下去的效果。所以如果正在执行的filter中isAccessAllowed和onAccessDenied都返回false,则整个filter控制链都将结束,不会到达目标方法(客户端请求的接口),而是直接跳转到某个页面(由filter定义的,将会在authc中看到)

AuthenticationFilter
isAccessAllowed

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

这里返回的subjec.isAuthenticated()就是AuthenticatingFilter中isAccessAllowed的super.isAccessAllowed。

从subject中isAuthenticated判断是否认证

AuthenticatingFilter
isAccessAllowed

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

AuthenticatingFilter中isAccessAllowed的实现了AuthenticationFilter中的isAccessAllowed抽象方法。

AuthenticationFilter和AuthenticatingFilter认证的filter,在抽象类中AuthenticatingFilter实现了isAccessAllowed方法,该方法是用来判断用户是否已登录,若未登录再判断是否请求的是登录地址,是登录地址则放行,否则返回false终止filter链。

isAccessAllowed方法是用来判断用户是否已登录,若未登录再判断是否请求的是登录地址,是登录地址则放行,否则返回false终止filter链

isLoginRequest(request, response)是用来判断客户端请求的地址是否是登陆地址

FormAuthenticationFiltershiro
FormAuthenticationFiltershiro提供的登录的filter,如果用户未登录,即AuthenticatingFilter中的isAccessAllowed判断了用户未登录,则会调用onAccessDenied方法做用户登录操作。

onAccessDenied

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

前面讲了:
isLoginRequest(request, response):用来判断客户端请求的地址是否是登陆地址
isLoginSubmission(request, response):用来判断客户端请求是什么方法,post还是get

若用户请求的不是登录地址,则跳转到登录地址,并且返回false直接终止filter链。
若用户请求的是登录地址,请求方法是post请求则进行登录操作,由AuthenticatingFilter中提供的executeLogin方法执行。
否则直接通过继续执行filter链,并最终跳转到登录页面(因为用户请求的就是登录地址,若不是登录地址也会重定向到登录地址)

executeLogin

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

onLoginSuccess

protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                     ServletRequest request, ServletResponse response) throws Exception {
        issueSuccessRedirect(request, response);
        //we handled the success redirect directly, prevent the chain from continuing:
        return false;
    }

onLoginFailure

protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                     ServletRequest request, ServletResponse response) {
        setFailureAttribute(request, e);
        //login failed, let request continue back to the login page:
        return true;
    }

补充:
我们会自定义过滤器例如:

那么这些自定义的filter需要我们加入过滤器链中。

1.重写FormAuthenticationFilter中的onLoginSuccess
它的父类是

我们既然重写了,那么执行时会执行我们重写的方法。
那么重写它能有什么作用了?
例如我们可以重写onLoginSuccess来设置使用ajax异步提交

/**
     * 
     * 当登录成功
     *
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        String requestType = ((HttpServletRequest)request).getHeader("X-Requested-With"); 
        if (requestType==null){// 不是ajax请求
            issueSuccessRedirect(request, response);
        } else {
            httpServletResponse.setCharacterEncoding("UTF-8");
            PrintWriter out = httpServletResponse.getWriter();
            out.println(JSONObject.toJSON(Resp.success()));
            out.flush();
            out.close();
        }
        return false;
    }

根据判断在请求头中是否有X-Requested-With,而判断是否有XMLHttpRequest对象,可以进行ajax提交,这样写可以在ajax回调函数中成功或者失败以json输出。

2. 重写FormAuthenticationFilter中的isAccessAllowed

我们需要在登陆后,更改session内容,我们需要重写isAccessAllowed,在其中对session进行设置。

这里需要把重写了isAccessAllowed方法的自定义过滤器加入过滤器链中


3.实现rememberMe记住我功能不用特地的重写isAccessAllowed
但是必须配置为 user级别,authc级别的rememberMe没有效果

过滤器链与认证过程
不知道有没有人和我一样产生过疑问,虽然知道了重写这些接口以及这个过滤器链执行的流程,也知道了认证过程,那么他们之间有啥关系吗?

在FormAuthenticationFilter过滤器的onAccessDenied方法中有executeLogin是执行登陆的

进入executeLogin

这里进行了登陆和认证

在配置文件中

在注册SecurityManager时初始化了,进入
org.apache.shiro.web.mgt.DefaultWebSecurityManager查看realms属性,

所以:认证的方法以及后面的认证的流程注入了SecurityManager中,而过滤器链执行流程中,在FormAuthenticationFilter过滤器的onAccessDenied方法中有executeLogin是执行了登陆和认证,从SecurityManager获取了Security对象也初始化了Security的认证。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

偷偷学习被我发现

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值