Filter

简介

对资源(Servlet或静态内容)的请求或者响应执行过滤任务;

每个过滤器可以通过FilterConfig获取自身初始化参数,以及ServletContext用来加载所需资源;

核心代码

/**
  * 初始化
  * 由Web容器调用,只会调用一次
  * 该方法执行成功,本过滤器才可以执行过滤工作
  */
public void init(FilterConfig filterConfig) throws ServletException;

/**
  * 过滤
  * 由Web容器调用,每个资源的请求经过过滤器链时,Web容器都会调用doFilter方法
  * FilterChain允许当前过滤器将请求传递到下一个过滤器
  */
public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException;

/**
  * 销毁
  * 由Web容器调用,只会调用一次,仅当doFilter方法中所有线程都退出或者超时后才会调用
  * 该方法执行后,Web容器不会再调用其doFilter方法
  */
public void destroy();

实现子类

实现类如下:

public interface Filter
    public abstract class AbstractFilter extends ServletContextSupport implements Filter    实现init(FilterConfig)算法模板和getInitParam(String),提供onFilterConfigSet()算法细节的默认实现,由子类扩展
        public abstract class NameableFilter extends AbstractFilter implements Nameable    增加name字段,默认为FilterConfig.getFilterName()
            public abstract class OncePerRequestFilter extends NameableFilter    增加enabled使能标识,实现doFilter(ServletRequest, ServletResponse, FilterChain)算法模板,提供抽象方法doFilterInternal(ServletRequest, ServletResponse, FilterChain)算法细节由子类实现
                public abstract class AbstractShiroFilter extends OncePerRequestFilter    实现onFilterConfigSet()/doFilterInternal(ServletRequest, ServletResponse, FilterChain)算法细节,提供init()算法细节的默认实现,由子类扩展
                    public class ShiroFilter extends AbstractShiroFilter    实现init()算法细节,设置SecurityManager/FilterChainResolver
                public abstract class AdviceFilter extends OncePerRequestFilter    实现doFilterInternal(ServletRequest, ServletResponse, FilterChain)/cleanup(ServletRequest, ServletResponse, Exception)算法模板,提供preHandle(ServletRequest, ServletResponse)/postHandle(ServletRequest, ServletResponse)/afterCompletion(ServletRequest, ServletResponse, Exception)算法细节的默认实现,由子类扩展
                    public class LogoutFilter extends AdviceFilter    实现preHandle(ServletRequest, ServletResponse)算法细节
                    public abstract class PathMatchingFilter extends AdviceFilter implements PathConfigProcessor    实现preHandle(ServletRequest, ServletResponse)算法模板和processPathConfig(String, String),提供onPreHandle(ServletRequest, ServletResponse, Object)算法细节的默认实现,由子类扩展
                        public class NoSessionCreationFilter extends PathMatchingFilter    实现onPreHandle(ServletRequest, ServletResponse, Object)算法细节
                        public class AnonymousFilter extends PathMatchingFilter    实现onPreHandle(ServletRequest, ServletResponse, Object)算法细节
                        public abstract class AccessControlFilter extends PathMatchingFilter    实现onPreHandle(ServletRequest, ServletResponse, Object)算法模板和saveRequestAndRedirectToLogin(ServletRequest, ServletResponse),提供抽象方法isAccessAllowed(ServletRequest, ServletResponse, Object)/onAccessDenied(ServletRequest, ServletResponse)算法细节由子类实现
                            public class UserFilter extends AccessControlFilter    实现isAccessAllowed(ServletRequest, ServletResponse, Object)/onAccessDenied(ServletRequest, ServletResponse)算法细节
                            public class InvalidRequestFilter extends AccessControlFilter    isAccessAllowed(ServletRequest, ServletResponse, Object)/onAccessDenied(ServletRequest, ServletResponse)
                            public abstract class AuthenticationFilter extends AccessControlFilter    实现isAccessAllowed(ServletRequest, ServletResponse, Object)算法细节
                                public class PassThruAuthenticationFilter extends AuthenticationFilter    实现onAccessDenied(ServletRequest, ServletResponse)算法细节
                                public abstract class AuthenticatingFilter extends AuthenticationFilter    实现isAccessAllowed(ServletRequest, ServletResponse, Object)/cleanup(ServletRequest, ServletResponse, Exception)算法细节,实现executeLogin(ServletRequest, ServletResponse)算法模板,提供抽象方法createToken(ServletRequest, ServletResponse)算法细节由子类实现,提供createToken(String, String, boolean, String)/onLoginSuccess(AuthenticationToken, Subject, ServletRequest, ServletResponse)/onLoginFailure(AuthenticationToken, Subject, ServletRequest, ServletResponse)算法细节的默认实现,由子类扩展
                                    public class FormAuthenticationFilter extends AuthenticatingFilter    实现onAccessDenied(ServletRequest, ServletResponse)/createToken(ServletRequest, ServletResponse)/onLoginSuccess(AuthenticationToken, Subject, ServletRequest, ServletResponse)/onLoginFailure(AuthenticationToken, Subject, ServletRequest, ServletResponse)算法细节
                                    abstract class HttpAuthenticationFilter extends AuthenticatingFilter    实现isAccessAllowed(ServletRequest, ServletResponse, Object)/onAccessDenied(ServletRequest, ServletResponse)/createToken(ServletRequest, ServletResponse)算法细节,提供抽象方法getPrincipalsAndCredentials(String, String)算法细节由子类实现
                                        public class BasicHttpAuthenticationFilter extends HttpAuthenticationFilter    实现createToken(ServletRequest, ServletResponse)/getPrincipalsAndCredentials(String, String)算法细节
                                        public class BearerHttpAuthenticationFilter extends HttpAuthenticationFilter    实现createToken(ServletRequest, ServletResponse)/getPrincipalsAndCredentials(String, String)算法细节
                            public abstract class AuthorizationFilter extends AccessControlFilter    实现onAccessDenied(ServletRequest, ServletResponse)算法细节
                                public class RolesAuthorizationFilter extends AuthorizationFilter    实现isAccessAllowed(ServletRequest, ServletResponse, Object)算法细节
                                public class PermissionsAuthorizationFilter extends AuthorizationFilter    实现isAccessAllowed(ServletRequest, ServletResponse, Object)算法细节
                                    public class HttpMethodPermissionFilter extends PermissionsAuthorizationFilter    实现isAccessAllowed(ServletRequest, ServletResponse, Object)算法细节
                                public class PortFilter extends AuthorizationFilter    实现isAccessAllowed(ServletRequest, ServletResponse, Object)/onAccessDenied(ServletRequest, ServletResponse)算法细节
                                    public class SslFilter extends PortFilter    实现isAccessAllowed(ServletRequest, ServletResponse, Object)/postHandle(ServletRequest, ServletResponse)算法细节
  • AbstractFilter

简介

Filter基础抽象类,实现初始化算法模版,提供获取初始化参数;子类可通过重写onFilterConfigSet()算法细节,实现自身的初始化逻辑;

FilterChain调用的doFilter()由子类实现;

核心代码

// 过滤器配置,由Servlet容器提供
protected FilterConfig filterConfig;

/**
  * 设置filterConfig
  */
public void setFilterConfig(FilterConfig filterConfig) {
    this.filterConfig = filterConfig;
    setServletContext(filterConfig.getServletContext());
}

/**
  * 获取初始化参数
  */
protected String getInitParam(String paramName) {
    FilterConfig config = getFilterConfig();
    if (config != null) {
        return StringUtils.clean(config.getInitParameter(paramName));
    }
    return null;
}

/**
  * 初始化
  * 算法模板
  */
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);
        }
    }
}

/**
  * 过滤器配置设置时处理
  * 算法细节,由子类重写,增加额外处理逻辑
  */
protected void onFilterConfigSet() throws Exception {
}

/**
  * 销毁
  */
public void destroy() {
}
  • NameableFilter

简介

支持设置名称的Filter;名称默认值为:FilterConfig.getFilterName();

核心代码

// 过滤器名称,应用程序内唯一
private String name;

/**
  * 获取名称
  */
protected String getName() {
    if (this.name == null) {
        // 未设置时,默认取FilterConfig.getFilterName()
        FilterConfig config = getFilterConfig();
        if (config != null) {
            this.name = config.getFilterName();
        }
    }


    return this.name;
}
  • OncePerRequestFilter

简介

保证在每次请求中只被执行一次的过滤器;

核心代码

// 使能标识,true表示执行过滤,否则放行请求
private boolean enabled = true;

/**
  * 过滤
  * 算法模板
  */
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);
        }
    }
}

/**
  * 是否需要执行过滤逻辑
  */
protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException {
    // 默认直接返回enable,子类可重写如必要
    return isEnabled();
}

/**
  * 过滤
  * 算法细节,由子类实现
  */
protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException;
  • AbstractShiroFilter

简介

提供标准Shiro请求的过滤逻辑的基础抽象类,期望子类实现特定于配置的逻辑;

子类需要重写init()方法执行配置和构造逻辑,通过setSecurityManager(WebSecurityManager)和setFilterChainResolver(FilterChainResolver)方法设置已存在的SecurityManager和FilterChainResolver;

核心代码

private WebSecurityManager securityManager;
private FilterChainResolver filterChainResolver;
// 在静态内存中启用本实例启用的SecurityManager实例标识
private boolean staticSecurityManagerEnabled;

/**
  * 构造方法
  */
protected AbstractShiroFilter() {
    // 默认不使用SecurityUtils.setSecurityManager
    // 期望Subject实例通过此过滤器实例在请求处理线程上构造
    this.staticSecurityManagerEnabled = false;
}

/**
  * 过滤器配置设置时处理
  * 算法模板
  */
protected final void onFilterConfigSet() throws Exception {
    // 应用在静态内存中启用本实例启用的SecurityManager实例标识配置
    //added in 1.2 for SHIRO-287:
    applyStaticSecurityManagerEnabledConfig();
    // 初始化
    init();
    // 确保SecurityManager有效
    ensureSecurityManager();
    //added in 1.2 for SHIRO-287:
    if (isStaticSecurityManagerEnabled()) {
        // 在静态内存中启用本实例启用的SecurityManager实例
        SecurityUtils.setSecurityManager(getSecurityManager());
    }
}

/**
  * 应用在静态内存中启用本实例启用的SecurityManager实例标识配置
  */
private void applyStaticSecurityManagerEnabledConfig() {
    // 获取在静态内存中启用本实例启用的SecurityManager实例标识配置
    String value = getInitParam(STATIC_INIT_PARAM_NAME);
    if (value != null) {
        Boolean b = Boolean.valueOf(value);
        if (b != null) {
            // 不为null则应用该配置
            setStaticSecurityManagerEnabled(b);
        }
    }
}

/**
  * 初始化
  * 算法细节,由子类重写,实现额外处理逻辑
  */
public void init() throws Exception {
}

/**
  * 确保SecurityManager有效
  */
private void ensureSecurityManager() {
    WebSecurityManager securityManager = getSecurityManager();
    if (securityManager == null) {
        // 若SecurityManager实例不存在则创建默认实例
        log.info("No SecurityManager configured.  Creating default.");
        securityManager = createDefaultSecurityManager();
        setSecurityManager(securityManager);
    }
}

/**
  * 创建默认SecurityManager实例
  */
protected WebSecurityManager createDefaultSecurityManager() {
    // 默认使用DefaultWebSecurityManager
    return new DefaultWebSecurityManager();
}

/**
  * 是否是HttpSession
  */
protected boolean isHttpSessions() {
    return getSecurityManager().isHttpSessionMode();
}

/**
  * 包装ServletRequest为ShiroHttpServletRequest
  */
protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
    return new ShiroHttpServletRequest(orig, getServletContext(), isHttpSessions());
}

/**
  * 预处理ServletRequest
  */
protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
    ServletRequest toUse = request;
    if (request instanceof HttpServletRequest) {
        // 如果是HttpServletRequest则需要包装为ShiroHttpServletRequest
        HttpServletRequest http = (HttpServletRequest) request;
        toUse = wrapServletRequest(http);
    }
    return toUse;
}

/**
  * 包装ServletResponse为ShiroHttpServletResponse
  */
protected ServletResponse wrapServletResponse(HttpServletResponse orig, ShiroHttpServletRequest request) {
    return new ShiroHttpServletResponse(orig, getServletContext(), request);
}

/**
  * 预处理ServletResponse
  */
protected ServletResponse prepareServletResponse(ServletRequest request, ServletResponse response, FilterChain chain) {
    ServletResponse toUse = response;
    if (!isHttpSessions() && (request instanceof ShiroHttpServletRequest) &&
            (response instanceof HttpServletResponse)) {
        //the ShiroHttpServletResponse exists to support URL rewriting for session ids.  This is only needed if
        //using Shiro sessions (i.e. not simple HttpSession based sessions):
        toUse = wrapServletResponse((HttpServletResponse) response, (ShiroHttpServletRequest) request);
    }
    return toUse;
}

/**
  * 创建Subject
  */
protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
    return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}

/**
  * 更新本地Session的最近一次访问时间
  */
protected void updateSessionLastAccessTime(ServletRequest request, ServletResponse response) {
    if (!isHttpSessions()) { //'native’ sessions
        // 是本地Session
        Subject subject = SecurityUtils.getSubject();
        //Subject should never _ever_ be null, but just in case:
        if (subject != null) {
            Session session = subject.getSession(false);
            if (session != null) {
                try {
                    // 更新最近一次访问时间
                    session.touch();
                } catch (Throwable t) {
                    log.error("session.touch() method invocation has failed.  Unable to update " +
                            "the corresponding session's last access time based on the incoming request.", t);
                }
            }
        }
    }
}

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

    Throwable t = null;

    try {
        // 预处理ServletRequest和ServletResponse
        final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
        final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

        // 创建Subject
        final Subject subject = createSubject(request, response);

        // 创建当前线程执行的任务并执行
        //noinspection unchecked
        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);
    }
}

/**
  * 根据指定的ServletRequest和ServletResponse获取过滤器链
  */
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {

    FilterChain chain = origChain;

    FilterChainResolver resolver = getFilterChainResolver();
    if (resolver == null) {
        // 未配置FilterChainResolver,则直接返回原始过滤器链
        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;
}

/**
  * 执行过滤器链
  */
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
        throws IOException, ServletException {
    // 获取指定ServletRequest绑定的过滤器链
    FilterChain chain = getExecutionChain(request, response, origChain);
    // 放行,交给下一个过滤器处理
    chain.doFilter(request, response);
}
  • ShiroFilter

简介

Web应用程序基于web.xml配置Shiro的首要Shiro过滤器;

期望ServletContext中存在基于web.xml配置的Shiro WebEnvironment;

核心代码

/**
  * 初始化
  * 基于已存在的WebEnvironment实例配置SecurityManager和FilterChainResolver
  */
@Override
public void init() throws Exception {
    WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext());

    // 设置WebSecurityManager
    setSecurityManager(env.getWebSecurityManager());

    FilterChainResolver resolver = env.getFilterChainResolver();
    if (resolver != null) {
        // 设置FilterChainResolver
        setFilterChainResolver(resolver);
    }
}
  • AdviceFilter

简介

通过preHandle()、postHandle()、afterCompletion()方法为ServletRequest启用AOP风格的环绕通知;

核心代码

/**
  * 预处理
  * 是否允许过滤器链继续执行
  * 算法细节,由子类重写,实现额外处理逻辑
  */
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
    return true;
}

/**
  * 后处理
  * 前提是过滤器链执行期间未发生异常
  * 算法细节,由子类重写,实现额外处理逻辑
  */
protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
}

/**
  * 完成后处理
  * 不论preHandle返回false还是过滤器链执行期间发生异常都会执行(finally代码块)
  * 算法细节,由子类重写,实现额外处理逻辑
  */
public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
}

/**
  * 执行过滤器链
  */
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception {
    // 放行,交给下一个过滤器处理
    chain.doFilter(request, response);
}

/**
  * 过滤
  * 算法模板
  */
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);
    }
}

/**
  * 清理资源
  */
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);
        }
    }
}
  • LogoutFilter

简介

登出过滤器,收到请求后,立即注销当前Subject,然后重定向到配置的redirectUrl;

核心代码

// 默认的重定向URL
public static final String DEFAULT_REDIRECT_URL = "/";
// 重定向URL
private String redirectUrl = DEFAULT_REDIRECT_URL;
// 仅支持POST请求标识
private boolean postOnlyLogout = false;

/**
  * 预处理
  */
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
    // 获取当前Subject
    Subject subject = getSubject(request, response);

    // Check if POST only logout is enabled
    if (isPostOnlyLogout()) {
        // 校验请求方式
        // check if the current request's method is a POST, if not redirect
        if (!WebUtils.toHttp(request).getMethod().toUpperCase(Locale.ENGLISH).equals("POST")) {
           return onLogoutRequestNotAPost(request, response);
        }
    }

    String redirectUrl = getRedirectUrl(request, response, subject);
    //try/catch added for SHIRO-298:
    try {
        subject.logout();
    } catch (SessionException ise) {
        log.debug("Encountered session exception during logout.  This can generally safely be ignored.", ise);
    }
    issueRedirect(request, response, redirectUrl);
    // 登出后不应进行进一步的交互
    return false;
}

/**
  * 获取当前Subject
  */
protected Subject getSubject(ServletRequest request, ServletResponse response) {
    // 直接返回SecurityUtils.getSubject()
    // 子类可重写获取逻辑
    return SecurityUtils.getSubject();
}

/**
  * 重定向到指定URL
  */
protected void issueRedirect(ServletRequest request, ServletResponse response, String redirectUrl) throws Exception {
    WebUtils.issueRedirect(request, response, redirectUrl);
}

/**
  * 获取重定向URL
  */
protected String getRedirectUrl(ServletRequest request, ServletResponse response, Subject subject) {
    // 直接返回redirectUrl
    // 子类可重写获取逻辑
    return getRedirectUrl();
}

/**
  * 登出请求非POST方式处理
  */
protected boolean onLogoutRequestNotAPost(ServletRequest request, ServletResponse response) {
    HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
    // 响应405
    httpServletResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
    httpServletResponse.setHeader("Allow", "POST");
    // 不进行进一步的交互
    return false;
}
  • PathMatchingFilter

简介

支持路径匹配的基础抽象过滤器;仅拦截指定的路径,其它路径直接放行;

核心代码

// 默认路径分隔符
private static final String DEFAULT_PATH_SEPARATOR = "/";
// 正则匹配器
protected PatternMatcher pathMatcher = new AntPathMatcher();
// 需要处理的路径集合
protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();

/**
  * 处理路径配置
  */
public Filter processPathConfig(String path, String config) {
    String[] values = null;
    if (config != null) {
        values = split(config);
    }

    // 保存路径配置
    this.appliedPaths.put(path, values);
    return this;
}

/**
  * 获取应用程序内的上下文路径
  */
protected String getPathWithinApplication(ServletRequest request) {
    // 子类可重写获取逻辑
    return WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
}

/**
  * 路径匹配
  */
protected boolean pathsMatch(String path, ServletRequest request) {
    // 获取应用程序内的上下文路径
    String requestURI = getPathWithinApplication(request);

    log.trace("Attempting to match pattern '{}' with current requestURI '{}'...", path, Encode.forHtml(requestURI));
    boolean match = pathsMatch(path, requestURI);

    if (!match) {
        // 未匹配,则去除路径末尾的路径分隔符后再进行一次路径匹配
        if (requestURI != null && !DEFAULT_PATH_SEPARATOR.equals(requestURI)
            && requestURI.endsWith(DEFAULT_PATH_SEPARATOR)) {
            requestURI = requestURI.substring(0, requestURI.length() - 1);
        }
        if (path != null && !DEFAULT_PATH_SEPARATOR.equals(path)
            && path.endsWith(DEFAULT_PATH_SEPARATOR)) {
            path = path.substring(0, path.length() - 1);
        }
        log.trace("Attempting to match pattern '{}' with current requestURI '{}'...", path, Encode.forHtml(requestURI));
        match = pathsMatch(path, requestURI);
    }

    return match;
}

/**
  * 路径匹配
  */
protected boolean pathsMatch(String pattern, String path) {
    boolean matches = pathMatcher.matches(pattern, path);
    log.trace("Pattern [{}] matches path [{}] => [{}]", pattern, path, matches);
    return matches;
}

/**
  * 预处理
  */
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;
}

/**
  * 是否继续执行过滤器链
  * 算法模板
  */
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;
}

/**
  * 预处理时处理
  * 算法细节,由子类重写,实现额外逻辑处理
  */
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    // 默认返回true
    // 由子类实现定制化逻辑
    return true;
}

/**
  * 是否需要执行过滤逻辑
  */
protected boolean isEnabled(ServletRequest request, ServletResponse response, String path, Object mappedValue)
        throws Exception {
    return isEnabled(request, response);
}
  • NoSessionCreationFilter

简介

禁止在请求期间创建新会话的PathMatchingFilter实现,可放置在任何导致不打算参与会话的服务的过滤器链前;

本过滤器可以保证以下行为:

  1. 如果调用本过滤器时,当前Subject还没有会话,那么在请求期间如果调用subject.getSession()和subject.getSession(true)就会抛出异常;

  2. 如果调用本过滤器时,当前Subject已经有会话,本过滤器将无任何效果;

核心代码

/**
  * 预处理
  */
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    // 禁止创建新会话
    request.setAttribute(DefaultSubjectContext.SESSION_CREATION_ENABLED, Boolean.FALSE);
    return true;
}
  • AnonymousFilter

简介

支持立即访问路径而不执行任何类型的安全检查的过滤器;

核心代码

/**
  * 预处理
  */
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
    // 始终返回true,不对路径做任何安全检查
    // Always return true since we allow access to anyone
    return true;
}
  • AccessControlFilter

简介

控制资源访问并支持重定向到登录页面的超类过滤器;

提供saveRequestAndRedirectToLogin(ServletRequest, ServletResponse)方法供子类调用;

核心代码

// 默认登录URL
public static final String DEFAULT_LOGIN_URL = "/login.jsp";
// 登录URL
private String loginUrl = DEFAULT_LOGIN_URL;

/**
  * 是否允许指定请求访问
  * 算法细节,由子类实现
  */
protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;

/**
  * 请求被拒绝时处理
  * 算法模板
  */
protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    return onAccessDenied(request, response);
}

/**
  * 请求被拒绝时处理
  * 算法细节,由子类实现
  */
protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;

/**
  * 预处理时处理
  */
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    // 是否允许指定请求访问
    return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

/**
  * 是否是登录请求
  */
protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
    return pathsMatch(getLoginUrl(), request);
}

/**
  * 保存当前请求并重定向到登录页面
  */
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
    // 保存当前请求
    saveRequest(request);
    // 重定向到登录页面
    redirectToLogin(request, response);
}

/**
  * 保存当前请求
  */
protected void saveRequest(ServletRequest request) {
    WebUtils.saveRequest(request);
}

/**
  * 重定向到登录页面
  */
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
    String loginUrl = getLoginUrl();
    WebUtils.issueRedirect(request, response, loginUrl);
}
  • UserFilter

简介

基于用户进行资源访问控制的过滤器;

如果当前用户已知,不论是已登录,或者被记住,则允许访问;否则重定向到登录页面;

核心代码

/**
  * 是否允许指定请求访问
  */
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    if (isLoginRequest(request, response)) {
        // 如果是登录请求直接放行
        return true;
    } else {
        Subject subject = getSubject(request, response);
        // 当前用户是否存在,不论是鉴权过的还是记住的用户
        // If principal is not null, then the user is known and should be allowed access.
        return subject.getPrincipal() != null;
    }
}

/**
  * 请求被拒绝时处理
  */
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    // 保存当前请求并重定向到登录页面
    saveRequestAndRedirectToLogin(request, response);
    return false;
}
  • InvalidRequestFilter

简介

拦截恶意请求的过滤器,恶意请求会返回400;

核心代码

/**
  * 是否允许指定请求访问
  */
@Override
protected boolean isAccessAllowed(ServletRequest req, ServletResponse response, Object mappedValue) throws Exception {
    HttpServletRequest request = WebUtils.toHttp(req);
    // check the original and decoded values
    return isValid(request.getRequestURI())      // user request string (not decoded)
            && isValid(request.getServletPath()) // decoded servlet part
            && isValid(request.getPathInfo());   // decoded path info (may be null)
}

/**
  * 请求是否有效
  */
private boolean isValid(String uri) {
    return !StringUtils.hasText(uri)
           || ( !containsSemicolon(uri)
             && !containsBackslash(uri)
             && !containsNonAsciiCharacters(uri));
}

/**
  * 请求被拒绝时处理
  */
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    WebUtils.toHttp(response).sendError(400, "Invalid request");
    return false;
}
  • AuthenticationFilter

简介

要求鉴权当前用户的基础抽象过滤器;

该类封装了检查用户是否已鉴权的逻辑,子类需要实现对未鉴权的请求执行特定逻辑;

核心代码

// 默认鉴权成功跳转URL
public static final String DEFAULT_SUCCESS_URL = "/";
// 鉴权成功跳转URL
private String successUrl = DEFAULT_SUCCESS_URL;

/**
  * 是否允许指定请求访问
  */
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    Subject subject = getSubject(request, response);
    // 当前用户是否已鉴权
    return subject.isAuthenticated();
}

/**
  * 登录成功后重定向到之前的请求路径
  */
protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
    WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
}
  • PassThruAuthenticationFilter

简介

放行登录请求的鉴权过滤器;

如果自定义MVC登录控制器或验证器,则该过滤器可能是合适的;

核心代码

/**
  * 请求被拒绝时处理
  */
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    if (isLoginRequest(request, response)) {
        // 如果是登录请求直接放行
        return true;
    } else {
        // 否则保存当前请求并重定向到登录页面
        saveRequestAndRedirectToLogin(request, response);
        return false;
    }
}
  • AuthenticatingFilter

简介

根据传入的请求自动执行鉴权尝试的鉴权过滤器;

核心代码

/**
  * 执行登录
  * 算法模板
  */
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
    // 创建AuthenticationToken
    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);
    }
}

/**
  * 创建AuthenticationToken
  * 算法细节,由子类实现
  */
protected abstract AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception;

/**
  * 创建AuthenticationToken
  */
protected AuthenticationToken createToken(String username, String password,
                                          ServletRequest request, ServletResponse response) {
    boolean rememberMe = isRememberMe(request);
    String host = getHost(request);
    return createToken(username, password, rememberMe, host);
}

/**
  * 创建AuthenticationToken
  */
protected AuthenticationToken createToken(String username, String password,
                                          boolean rememberMe, String host) {
    // 默认创建UsernamePasswordToken
    return new UsernamePasswordToken(username, password, rememberMe, host);
}

/**
  * 登录成功时
  * 算法细节,由子类重写,增加额外处理逻辑
  */
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                 ServletRequest request, ServletResponse response) throws Exception {
    return true;
}

/**
  * 登录失败时处理
  * 算法细节,由子类重写,增加额外处理逻辑
  */
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                 ServletRequest request, ServletResponse response) {
    return false;
}

/**
  * 获取Subject的host信息
  */
protected String getHost(ServletRequest request) {
    return request.getRemoteHost();
}

/**
  * 是否使能RememberMe服务
  * 算法细节,由子类重写,增加额外处理逻辑
  */
protected boolean isRememberMe(ServletRequest request) {
    // 默认不开启
    return false;
}

/**
  * 是否允许指定请求访问
  */
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    // 当前用户是否已鉴权,或者非登录请求是否允许放行
    return super.isAccessAllowed(request, response, mappedValue) ||
            (!isLoginRequest(request, response) && isPermissive(mappedValue));
}

/**
  * 是否允许放行
  */
protected boolean isPermissive(Object mappedValue) {
    if(mappedValue != null) {
        String[] values = (String[]) mappedValue;
        return Arrays.binarySearch(values, PERMISSIVE) >= 0;
    }
    return false;
}

/**
  * 清理资源
  */
@Override
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);
}
  • FormAuthenticationFilter

简介

要求请求用户已被鉴权的过滤器;

如果是登录请求,则进一步判断是否是登陆提交请求,是则进行登陆操作,否则直接放行;非登录请求则重定向到配置的登录页面,强制用户完成登录后才可以继续访问资源;

核心代码

public static final String DEFAULT_ERROR_KEY_ATTRIBUTE_NAME = "shiroLoginFailure";
public static final String DEFAULT_USERNAME_PARAM = "username";
public static final String DEFAULT_PASSWORD_PARAM = "password";
public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe";
// 请求中用户名信息属性
private String usernameParam = DEFAULT_USERNAME_PARAM;
// 请求中密码信息属性
private String passwordParam = DEFAULT_PASSWORD_PARAM;
// 请求中RememberMe信息属性
private String rememberMeParam = DEFAULT_REMEMBER_ME_PARAM;
private String failureKeyAttribute = DEFAULT_ERROR_KEY_ATTRIBUTE_NAME;

/**
  * 构造方法
  */
public FormAuthenticationFilter() {
    setLoginUrl(DEFAULT_LOGIN_URL);
}

/**
  * 设置登录路径
  */
@Override
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);
}

/**
  * 请求被拒绝时处理
  */
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;
    }
}

/**
  * 是否是登录提交请求
  */
protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
    return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);
}

/**
  * 创建AuthenticationToken
  * 默认调用父类方法创建UsernamePasswordToken
  */
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
    String username = getUsername(request);
    String password = getPassword(request);
    return createToken(username, password, request, response);
}

/**
  * 获取指定请求中的RememberMe信息
  */
protected boolean isRememberMe(ServletRequest request) {
    return WebUtils.isTrue(request, getRememberMeParam());
}

/**
  * 登录成功时处理
  */
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;
}

/**
  * 登录失败时处理
  */
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                 ServletRequest request, ServletResponse response) {
    if (log.isDebugEnabled()) {
        log.debug( "Authentication exception", e );
    }
    // 设置失败属性
    setFailureAttribute(request, e);
    //login failed, let request continue back to the login page:
    return true;
}

/**
  * 设置失败属性
  */
protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
    String className = ae.getClass().getName();
    request.setAttribute(getFailureKeyAttribute(), className);
}

/**
  * 获取指定请求中的用户名信息
  */
protected String getUsername(ServletRequest request) {
    return WebUtils.getCleanParam(request, getUsernameParam());
}

/**
  * 获取指定请求中的密码信息 
  */
protected String getPassword(ServletRequest request) {
    return WebUtils.getCleanParam(request, getPasswordParam());
}
  • HttpAuthenticationFilter

简介

支持只拦截配置的HTTP Method,未配置则默认拦截;根据HTTP Authorization 请求头判断是否是登录请求,如果是则执行登录;登录失败或者非登录请求则返回401;

核心代码

// 授权请求头字段
protected static final String AUTHORIZATION_HEADER = "Authorization";
// 鉴权请求头字段
protected static final String AUTHENTICATE_HEADER = "WWW-Authenticate";
// 应用程序名称
private String applicationName = "application";
// 查找HTTP WWW-Authenticate 头字段方案
private String authcScheme;
// 查找HTTP Authorization 头字段方案
private String authzScheme;

/**
  * 是否允许指定请求访问
  */
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    HttpServletRequest httpRequest = WebUtils.toHttp(request);
    String httpMethod = httpRequest.getMethod();

    // Check whether the current request's method requires authentication.
    // If no methods have been configured, then all of them require auth,
    // otherwise only the declared ones need authentication.

    // 校验HTTP Method
    Set<String> methods = httpMethodsFromOptions((String[])mappedValue);
    // 未配置则默认需要鉴权
    boolean authcRequired = methods.size() == 0;
    for (String m : methods) {
        if (httpMethod.toUpperCase(Locale.ENGLISH).equals(m)) { // list of methods is in upper case
            // 在配置中则需要鉴权
            authcRequired = true;
            break;
        }
    }

    if (authcRequired) {
        // 调用父类方法进行鉴权
        return super.isAccessAllowed(request, response, mappedValue);
    }
    else {
        return true;
    }
}

/**
  * 将配置参数转换成HTTP Method
  */
private Set<String> httpMethodsFromOptions(String[] options) {
    Set<String> methods = new HashSet<String>();

    if (options != null) {
        for (String option : options) {
            // to be backwards compatible with 1.3, we can ONLY check for known args
            // ideally we would just validate HTTP methods, but someone could already be using this for webdav
            if (!option.equalsIgnoreCase(PERMISSIVE)) {
                methods.add(option.toUpperCase(Locale.ENGLISH));
            }
        }
    }
    return methods;
}

/**
  * 请求被拒绝时处理
  * 处理 两阶段提交 鉴权协议
  */
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    boolean loggedIn = false; //false by default or we wouldn't be in this method
    if (isLoginAttempt(request, response)) {
        loggedIn = executeLogin(request, response);
    }
    if (!loggedIn) {
        // 登录失败则返回401
        sendChallenge(request, response);
    }
    return loggedIn;
}

/**
  * 是否是登录尝试
  * 默认实现是判断请求头AUTHORIZATION_HEADER字段内容
  */
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
    String authzHeader = getAuthzHeader(request);
    return authzHeader != null && isLoginAttempt(authzHeader);
}

/**
  * 是否是登录请求
  */
@Override
protected final boolean isLoginRequest(ServletRequest request, ServletResponse response) {
    return this.isLoginAttempt(request, response);
}

/**
  * 获取请求头AUTHORIZATION_HEADER字段内容
  */
protected String getAuthzHeader(ServletRequest request) {
    HttpServletRequest httpRequest = WebUtils.toHttp(request);
    return httpRequest.getHeader(AUTHORIZATION_HEADER);
}

/**
  * 是否是登录尝试
  */
protected boolean isLoginAttempt(String authzHeader) {
    //SHIRO-415: use English Locale:
    String authzScheme = getAuthzScheme().toLowerCase(Locale.ENGLISH);
    return authzHeader.toLowerCase(Locale.ENGLISH).startsWith(authzScheme);
}

/**
  * 发送质询
  */
protected boolean sendChallenge(ServletRequest request, ServletResponse response) {
    log.debug("Authentication required: sending 401 Authentication challenge response.");


    HttpServletResponse httpResponse = WebUtils.toHttp(response);
    // 设置401响应码
    httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    String authcHeader = getAuthcScheme() + " realm=\"" + getApplicationName() + "\"";
    // 设置响应头AUTHENTICATE_HEADER字段
    httpResponse.setHeader(AUTHENTICATE_HEADER, authcHeader);
    return false;
}

/**
  * 创建AuthenticationToken
  */
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
    String authorizationHeader = getAuthzHeader(request);
    if (authorizationHeader == null || authorizationHeader.length() == 0) {
        // 请求头(AUTHORIZATION_HEADER字段内容为空则构造空AuthenticationToken
        // Create an empty authentication token since there is no
        // Authorization header.
        return createToken("", "", request, response);
    }

    log.debug("Attempting to execute login with auth header");

    // 获取用户名和密码信息
    String[] prinCred = getPrincipalsAndCredentials(authorizationHeader, request);
    if (prinCred == null || prinCred.length < 2) {
        // 仅存在用户名信息
        // Create an authentication token with an empty password,
        // since one hasn't been provided in the request.
        String username = prinCred == null || prinCred.length == 0 ? "" : prinCred[0];
        // 根据用户名/密码创建UsernamePasswordToken
        return createToken(username, "", request, response);
    }

    String username = prinCred[0];
    String password = prinCred[1];

    // 根据用户名/密码创建UsernamePasswordToken
    return createToken(username, password, request, response);
}

/**
  * 获取用户名和密码信息
  * 算法模板
  */
protected String[] getPrincipalsAndCredentials(String authorizationHeader, ServletRequest request) {
    if (authorizationHeader == null) {
        return null;
    }
    String[] authTokens = authorizationHeader.split(" ");
    if (authTokens == null || authTokens.length < 2) {
        return null;
    }
    return getPrincipalsAndCredentials(authTokens[0], authTokens[1]);
}

/**
  * 获取用户名和密码信息
  * 算法细节,由子类实现
  */
abstract String[] getPrincipalsAndCredentials(String scheme, String value);
  • BasicHttpAuthenticationFilter

简介

基于HTTP Basic协议(使用UsernamePasswordToken)进行登录尝试;

核心代码

/**
  * 构造方法
  */
public BasicHttpAuthenticationFilter() {
    // 默认使用BASIC
    setAuthcScheme(HttpServletRequest.BASIC_AUTH);
    setAuthzScheme(HttpServletRequest.BASIC_AUTH);
}

/**
  * 创建AuthenticationToken
  */
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
    String authorizationHeader = getAuthzHeader(request);
    if (authorizationHeader == null || authorizationHeader.length() == 0) {
        // 请求头(AUTHORIZATION_HEADER字段内容为空则构造空AuthenticationToken
        // Create an empty authentication token since there is no
        // Authorization header.
        return createToken("", "", request, response);
    }

    log.debug("Attempting to execute login with auth header");

    // 获取用户名和密码信息
    String[] prinCred = getPrincipalsAndCredentials(authorizationHeader, request);
    if (prinCred == null || prinCred.length < 2) {
        // 仅存在用户名信息
        // Create an authentication token with an empty password,
        // since one hasn't been provided in the request.
        String username = prinCred == null || prinCred.length == 0 ? "" : prinCred[0];
        return createToken(username, "", request, response);
    }

    String username = prinCred[0];
    String password = prinCred[1];

    // 根据用户名/密码创建UsernamePasswordToken
    return createToken(username, password, request, response);
}

/**
  * 获取用户名和密码信息
  */
protected String[] getPrincipalsAndCredentials(String scheme, String encoded) {
    // base64编码用户名和密码
    String decoded = Base64.decodeToString(encoded);
    return decoded.split(":", 2);
}
  • BearerHttpAuthenticationFilter

简介

基于HTTP Bearer协议(使用BearerToken)进行登录尝试;

核心代码

private static final String BEARER = "Bearer";

/**
  * 构造方法
  */
public BearerHttpAuthenticationFilter() {
    // 默认使用Bearer
    setAuthcScheme(BEARER);
    setAuthzScheme(BEARER);
}

/**
  * 创建AuthenticationToken
  */
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
    String authorizationHeader = getAuthzHeader(request);
    if (authorizationHeader == null || authorizationHeader.length() == 0) {
        // 请求头(AUTHORIZATION_HEADER字段内容为空则构造空BearerToken
        // Create an empty authentication token since there is no
        // Authorization header.
        return createBearerToken("", request);
    }

    log.debug("Attempting to execute login with auth header");

    // 获取token信息
    String[] prinCred = getPrincipalsAndCredentials(authorizationHeader, request);
    if (prinCred == null || prinCred.length < 1) {
        // 不存在token信息则构造空BearerToken
        // Create an authentication token with an empty password,
        // since one hasn't been provided in the request.
        return createBearerToken("", request);
    }

    // 根据token创建BearerToken
    String token = prinCred[0] != null ? prinCred[0] : "";
    return createBearerToken(token, request);
}

/**
  * 获取用户名和密码信息
  */
@Override
protected String[] getPrincipalsAndCredentials(String scheme, String token) {
    return new String[] {token};
}

/**
  * 创建BearerToken
  */
protected AuthenticationToken createBearerToken(String token, ServletRequest request) {
    return new BearerToken(token, request.getRemoteHost());
}
  • AuthorizationFilter

简介

授权过滤器,实现对未授权请求的后续处理;

子类需要实现isAccessAllowed(ServletRequest, ServletResponse, Object) throws IOException;

核心代码

// 未授权跳转的路径
private String unauthorizedUrl;

/**
  * 请求被拒绝时处理
  */
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;
}
  • 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<String> roles = CollectionUtils.asSet(rolesArray);
    // 登录用户是否拥有该角色集合
    return subject.hasAllRoles(roles);
}
  • PermissionsAuthorizationFilter

简介

基于权限授权的过滤器;

如果当前登录用户拥有指定的权限,则放行;没有,则拒绝访问;

核心代码

/**
  * 是否允许指定请求访问
  */
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {

    Subject subject = getSubject(request, response);
    String[] perms = (String[]) mappedValue;

    boolean isPermitted = true;
    if (perms != null && perms.length > 0) {
        // 只需要一个权限
        if (perms.length == 1) {
            if (!subject.isPermitted(perms[0])) {
                isPermitted = false;
            }
        } else {
            // 需要多个权限
            if (!subject.isPermittedAll(perms)) {
                isPermitted = false;
            }
        }
    }

    return isPermitted;
}
  • HttpMethodPermissionFilter

简介

基于权限授权的过滤器,支持将HTTP Method转换成对应的动作并构造新的权限进行访问控制;

核心代码

// 动作标识
private static final String CREATE_ACTION = "create";
private static final String READ_ACTION = "read";
private static final String UPDATE_ACTION = "update";
private static final String DELETE_ACTION = "delete";

/**
  * HTTP Method和动作的对应关系
  */
private static enum HttpMethodAction {

    DELETE(DELETE_ACTION),
    GET(READ_ACTION),
    HEAD(READ_ACTION),
    MKCOL(CREATE_ACTION), //webdav, but useful here
    OPTIONS(READ_ACTION),
    POST(CREATE_ACTION),
    PUT(UPDATE_ACTION),
    TRACE(READ_ACTION);

    private final String action;

    private HttpMethodAction(String action) {
        this.action = action;
    }

    public String getAction() {
        return this.action;
    }
}

/**
  * 构造方法
  */
public HttpMethodPermissionFilter() {
    for (HttpMethodAction methodAction : HttpMethodAction.values()) {
        httpMethodActions.put(methodAction.name().toLowerCase(), methodAction.getAction());
    }
}

/**
  * 获取HTTP Method对应的动作
  */
protected String getHttpMethodAction(String method) {
    String lc = method.toLowerCase();
    String resolved = getHttpMethodActions().get(lc);
    return resolved != null ? resolved : method;
}

/**
  * 构造权限数组
  */
protected String[] buildPermissions(String[] configuredPerms, String action) {
    // 无配置的权限或无有效动作
    if (configuredPerms == null || configuredPerms.length <= 0 || !StringUtils.hasText(action)) {
        return configuredPerms;
    }

    String[] mappedPerms = new String[configuredPerms.length];

    // loop and append :action
    for (int i = 0; i < configuredPerms.length; i++) {
        // 构造动作权限
        mappedPerms[i] = configuredPerms[i] + ":" + action;
    }

    if (log.isTraceEnabled()) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < mappedPerms.length; i++) {
            if (i > 0) {
                sb.append(", ");
            }
            sb.append(mappedPerms[i]);
        }
        log.trace("MAPPED '{}' action to permission(s) '{}'", action, sb);
    }

    return mappedPerms;
}

/**
  * 是否允许指定请求访问
  */
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
    String[] perms = (String[]) mappedValue;
    // append the http action to the end of the permissions and then back to super
    // 获取HTTP Method对应的动作
    String action = getHttpMethodAction(request);
    // 根据动作构造权限数组
    String[] resolvedPerms = buildPermissions(perms, action);
    // 判断是否拥有该权限数组
    return super.isAccessAllowed(request, response, resolvedPerms);
}
  • PortFilter

简介

基于端口授权的过滤器;要求请求来自配置的端口号(未配置则默认80),如果不满足,则重定向到新URL(将原URL的端口替换为配置的端口)的过滤器;

核心代码

// 请求默认配置端口
public static final int DEFAULT_HTTP_PORT = 80;
// HTTP协议
public static final String HTTP_SCHEME = "http";

/**
  * 获取配置的端口
  */
protected int toPort(Object mappedValue) {
    String[] ports = (String[]) mappedValue;
    if (ports == null || ports.length == 0) {
        // 未配置,则使用默认端口
        return getPort();
    }
    if (ports.length > 1) {
        throw new ConfigurationException("PortFilter can only be configured with a single port.  You have " +
                "configured " + ports.length + ": " + StringUtils.toString(ports));
    }
    return Integer.parseInt(ports[0]);
}

/**
  * 是否允许指定请求访问
  */
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    int requiredPort = toPort(mappedValue);
    int requestPort = request.getServerPort();
    // 请求的端口号和配置的端口号是否一致
    return requiredPort == requestPort;
}

/**
  * 获取协议
  */
protected String getScheme(String requestScheme, int port) {
    if (port == DEFAULT_HTTP_PORT) {
        // HTTP协议
        return HTTP_SCHEME;
    } else if (port == SslFilter.DEFAULT_HTTPS_PORT) {
        // HTTPS协议
        return SslFilter.HTTPS_SCHEME;
    } else {
        return requestScheme;
    }
}

/**
  * 请求被拒绝时处理
  */
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {

    // 获取配置端口号
    //just redirect to the specified port:
    int port = toPort(mappedValue);

    // 获取协议
    String scheme = getScheme(request.getScheme(), port);

    // 构造重定向URL
    StringBuilder sb = new StringBuilder();
    sb.append(scheme).append("://");
    sb.append(request.getServerName());
    if (port != DEFAULT_HTTP_PORT && port != SslFilter.DEFAULT_HTTPS_PORT) {
        sb.append(":");
        sb.append(port);
    }
    if (request instanceof HttpServletRequest) {
        sb.append(WebUtils.toHttp(request).getRequestURI());
        String query = WebUtils.toHttp(request).getQueryString();
        if (query != null) {
            sb.append("?").append(query);
        }
    }

    // 重定向到配置端口
    WebUtils.issueRedirect(request, response, sb.toString());


    return false;
}
  • SslFilter

简介

基于端口授权的过滤器;要求请求来自配置的端口号(未配置则默认443)并且是安全的,如果不满足,则重定向到新URL(将原URL的端口替换为配置的端口)的过滤器;

核心代码

// 请求默认配置端口
public static final int DEFAULT_HTTPS_PORT = 443;
// HTTPS协议
public static final String HTTPS_SCHEME = "https";
// HTTP Strict Transport Security
private HSTS hsts;

/**
  * 构造方法
  */
public SslFilter() {
    // 默认配置端口为443
    setPort(DEFAULT_HTTPS_PORT);
    this.hsts = new HSTS();
}

/**
  * 获取协议
  */
@Override
protected String getScheme(String requestScheme, int port) {
    if (port == DEFAULT_HTTP_PORT) {
        return PortFilter.HTTP_SCHEME;
    } else {
        return HTTPS_SCHEME;
    }
}

/**
  * 是否允许指定请求访问
  */
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    // 请求来自配置的端口号并且请求是安全的
    return super.isAccessAllowed(request, response, mappedValue) && request.isSecure();
}

/**
  * 后处理
  */
@Override
protected void postHandle(ServletRequest request, ServletResponse response)  {
    if (hsts.isEnabled()) {
        StringBuilder directives = new StringBuilder(64)
                .append("max-age=").append(hsts.getMaxAge());
        
        if (hsts.isIncludeSubDomains()) {
            directives.append("; includeSubDomains");
        }
        
        HttpServletResponse resp = (HttpServletResponse) response;
        resp.addHeader(HSTS.HTTP_HEADER, directives.toString());
    }
}

/**
  * HTTP Strict Transport Security 帮助类
  */
public class HSTS {
    
    public static final String HTTP_HEADER = "Strict-Transport-Security";
    // 默认关闭
    public static final boolean DEFAULT_ENABLED = false;
    public static final int DEFAULT_MAX_AGE = 31536000; // approx. one year in seconds
    public static final boolean DEFAULT_INCLUDE_SUB_DOMAINS = false;
    
    private boolean enabled;
    private int maxAge;
    private boolean includeSubDomains;
    
    public HSTS() {
        this.enabled = DEFAULT_ENABLED;
        this.maxAge = DEFAULT_MAX_AGE;
        this.includeSubDomains = DEFAULT_INCLUDE_SUB_DOMAINS;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public int getMaxAge() {
        return maxAge;
    }

    public void setMaxAge(int maxAge) {
        this.maxAge = maxAge;
    }

    public boolean isIncludeSubDomains() {
        return includeSubDomains;
    }

    public void setIncludeSubDomains(boolean includeSubDomains) {
        this.includeSubDomains = includeSubDomains;
    }
}
上一篇:JavaWeb 之


下一篇:【java】codegym学习笔记【核心篇】- OOP 基础知识:重载、多态、抽象、接口