Spring Shiro基础组件 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;
}

/**
 * 初始化
 * 模板方法,onFilterConfigSet()方法由子类实现逻辑处理
 */
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;

/**
 * 过滤
 * 模板方法,定义过滤逻辑的算法骨架,由子类实现doFilterInternal算法细节
 */
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)方法设置已构造的SecurityManagerFilterChainResolver

核心方法

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) {
    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
 */
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) {
        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];
        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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值