Spring-shiro源码陶冶-DefaultFilter

阅读源码有助于陶冶情操,本文旨在简单的分析shiro在Spring中的使用

简单介绍

Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能

Apache Shiro自带的默认Filter

直接查看DefaultFilter类便可以一目了然,具体代码如下

public enum DefaultFilter {
	//从此处可看,shiro默认的filter有11个
    anon(AnonymousFilter.class),
    authc(FormAuthenticationFilter.class),
    authcBasic(BasicHttpAuthenticationFilter.class),
    logout(LogoutFilter.class),
    noSessionCreation(NoSessionCreationFilter.class),
    perms(PermissionsAuthorizationFilter.class),
    port(PortFilter.class),
    rest(HttpMethodPermissionFilter.class),
    roles(RolesAuthorizationFilter.class),
    ssl(SslFilter.class),
    user(UserFilter.class);

    private final Class<? extends Filter> filterClass;
	//存放的是相关的filter类
    private DefaultFilter(Class<? extends Filter> filterClass) {
        this.filterClass = filterClass;
    }

    public Filter newInstance() {
        return (Filter) ClassUtils.newInstance(this.filterClass);
    }

    public Class<? extends Filter> getFilterClass() {
        return this.filterClass;
    }
	//创建map集合
    public static Map<String, Filter> createInstanceMap(FilterConfig config) {
        Map<String, Filter> filters = new LinkedHashMap<String, Filter>(values().length);
        for (DefaultFilter defaultFilter : values()) {
            Filter filter = defaultFilter.newInstance();
            if (config != null) {
                try {
                    filter.init(config);
                } catch (ServletException e) {
                    String msg = "Unable to correctly init default filter instance of type " +
                            filter.getClass().getName();
                    throw new IllegalStateException(msg, e);
                }
            }
            filters.put(defaultFilter.name(), filter);
        }
        return filters;
    }
}

从代码中可以查看得知拥有的默认Filter有11个,决定分类逐个分析他们,在此之前先观察下这几个Filter类的共性

默认Filter类的继承关系

大致采用了模板模式的设计模式

  1. OncePerRequestFilter抽象类-对每个请求只调用一次

     public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
            filterChain.doFilter(request, response);
        } else //noinspection deprecation
            if (!isEnabled(request, response) ||
                shouldNotFilter(request) ) {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                    getName());
            filterChain.doFilter(request, response);
        } else {
            // Do invoke this filter...
            log.trace("Filter '{}' not yet executed.  Executing now.", getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
    
            try {
    			//可见容子类去复写
                doFilterInternal(request, response, filterChain);
            } finally {
                // Once the request has finished, we're done and we don't
                // need to mark as 'already filtered' any more.
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }
    
  2. AdviceFilter抽象类-AOP风格,类似@Around注解

    //复写第一级的父类方法
    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) {
    			//在前处理返回true的情况下让过滤链往下走
                executeChain(request, response, chain);
            }
    		//后处理
            postHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Successfully invoked postHandle method");
            }
    
        } catch (Exception e) {
            exception = e;
        } finally {
            cleanup(request, response, exception);
        }
    }
    
  3. PathMatchingFilter抽象类-url匹配Filter类

    //复写第二级的父类方法
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
    	//如果没有匹配的url集合则表示所有的url都不拦截处理,即返回true
        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;
        }
    	//根据request中的url匹配
        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);
    			//此处的config一般指代permission、roles、port的标识集合
                Object config = this.appliedPaths.get(path);
    			//其实是调用onPreHandle(request, response, pathConfig)方法,此方法默认是返回true,可供子类复写
                return isFilterChainContinued(request, response, path, config);
            }
        }
    
        //no path matched, allow the request to go through:没有path匹配则放行
        return true;
    }
    
  4. AccessControlFilter extends PathMatchingFilter-资源访问控制抽象类,认证以及授权等Filter类均需继承此类

    	//复写父类onPreHandler方法
    	public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    		//优先判断isAccessAllowed()方法,再判断onAccessDenied()方法,其中的mappedValue参数为pathConfig,类同permission、roles、port的标识集合
    		//onAccessDenied()方法true代表放行,false则包装response,自行转发数据
    		return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
    	}
    
  5. AuthenticationFilter extends AccessControlFilter-用户认证Filter抽象类
    该抽象类只复写了其中的一个条件方法isAccessAllowed()

    	//判断当前对象是否已通过认证,Subject是接口对象类
    	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    		Subject subject = getSubject(request, response);
    		return subject.isAuthenticated();
    	}
    
  6. AuthenticatingFilter extends AuthenticationFilter-用户认证Filter补充抽象类,表明该类的实现类针对login请求

    //复写父类,额外增加通过率
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    	//在父类的基础上又额外添加了返回true的条件:1.不是login请求 2.pathConfig含有permissive字段
        return super.isAccessAllowed(request, response, mappedValue) ||
                (!isLoginRequest(request, response) && isPermissive(mappedValue));
    }
    

    另外此类涉及到了用户Token创建,可见如下代码清单

    //登录验证
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
    	//抽象类,供子类去实现完成创建token,且不允许Token为空
        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);
    		//此处的login方法其实调用了token认证接口
            subject.login(token);
    		//login方法无异常,则执行登录成功操作,供子类实现
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
    		//login方法有异常,则执行登录失败操作,此处也供子类实现
            return onLoginFailure(token, e, request, response);
        }
    }
    

默认的这些类大多都是继承AdviceFilter类或者PathMatchingFilter类,下面将从这两方面对shiro默认的Filter进行归纳

继承PathMatchingFilter抽象类

即需要复写其主要方法onPreHandle方法

  1. [anno]AnonymousFilter extends PathMatchingFilter-可见对此filter对应的url全部都放行

    @Override
    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
        // Always return true since we allow access to anyone
        return true;
    }
    
  2. [authc]FormAuthenticationFilter extends AuthenticatingFilter-针对表单提交的认证Filter类

    • 设定了表单提交时帐号与密码的参数名,默认为username、password,可通过<property name="usernameParam/passwordParam">设置
    • 复写了另外一个通过判断条件,即onAccessDenied()方法
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    	//首先判断必须为login请求
        if (isLoginRequest(request, response)) {
    		//判断必须是post类型的表单提交
            if (isLoginSubmission(request, response)) {
                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }
    			//调用super.executeLogin()方法来进行校验
                return executeLogin(request, response);
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }
                //allow them to see the login page ;)
    			//对login页面请求不拦截
                return true;
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                        "Authentication url [" + getLoginUrl() + "]");
            }
    		//否则跳转至login页面
            saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }
    
    • 复写了createToken()方法
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        String username = getUsername(request);
        String password = getPassword(request);
    	//主要创建UserpasswordToken对象
        return createToken(username, password, request, response);
    }
    
    • 复写了其中的onLoginSuccess和onLoginFailure()方法
    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) {
    	//直接返回true,让过滤链处理
            setFailureAttribute(request, e);
            //login failed, let request continue back to the login page:
            return true;
        }
    
  3. [authcBasic]BasicHttpAuthenticationFilter extends AuthenticatingFilter-基于http头的认证Filter

    • 同样复写了onAccessDenied方法
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        boolean loggedIn = false; //false by default or we wouldn't be in this method
    	//request header含有Authorization字段
        if (isLoginAttempt(request, response)) {
    		//调用super.executeLogin方法
            loggedIn = executeLogin(request, response);
        }
        if (!loggedIn) {
    		//登录失败则发送401状态错误以及设置WWW-Authenticate的信息`Basic realm="application"`到response的header
            sendChallenge(request, response);
        }
        return loggedIn;
    }
    
    • 同样复写了createToken()方法
    //主要从request请求的头部中的Authorization获取username/password信息
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        String authorizationHeader = getAuthzHeader(request);
    	//无Authorization头部信息则默认验证失败
        if (authorizationHeader == null || authorizationHeader.length() == 0) {
            // Create an empty authentication token since there is no
            // Authorization header.
            return createToken("", "", request, response);
        }
    
        if (log.isDebugEnabled()) {
            log.debug("Attempting to execute login with headers [" + authorizationHeader + "]");
        }
    	//如果存在应该为(scheme和Base64加密过的username:password组合) 两者以空格分割
        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];
    
        return createToken(username, password, request, response);
    }
    
  4. [noSessionCreation]NoSessionCreationFilter extends PathMatchingFilter-不允许创建session Filter类,其会在新Subject创建session时报错,原有的则不报错

    @Override
    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        request.setAttribute(DefaultSubjectContext.SESSION_CREATION_ENABLED, Boolean.FALSE);
        return true;
    }
    
  5. [perms]PermissionsAuthorizationFilter extends AuthorizationFilter-权限认证Filter类,其默认是获取pathConfig中字段并进行比对,一般来说我们需要自定义permissons集合

     ```java
      public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
     
         Subject subject = getSubject(request, response);
     //pathConfig
         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;
      }
     ```
    
  6. [port]PortFilter extends AuthorizationFilter-请求对特定的端口放行,否则则跳转指定端口的url 调用示例:port[8008]

    • 复写父类的isAccessAllowed()方法
    //对应的url必须是指定的端口,否则调用onAccessDenied()
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        int requiredPort = toPort(mappedValue);
        int requestPort = request.getServerPort();
        return requiredPort == requestPort;
    }
    
    • 复写父类的onAccessDenied()
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
    
        //just redirect to the specified port:默认为80端口
        int port = toPort(mappedValue);
    
        String scheme = getScheme(request.getScheme(), port);
    
        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;
    }
    
  7. [rest]HttpMethodPermissionFilter extends PermissionsAuthorizationFilter-rest风格的请求方法权限Filter类 调用示例:rest[perm1,perm2]-->程序封装:rest[perm1:get,perm:get]

    @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
        String action = getHttpMethodAction(request);
    	//对pathConfig中的内容进行封装,封装为perm:action的组合
        String[] resolvedPerms = buildPermissions(perms, action);
        return super.isAccessAllowed(request, response, resolvedPerms);
    }
    
  8. [roles]RolesAuthorizationFilter extends AuthorizationFilter-针对roles的权限认证,调用示例:roles[role1,role2]即对指定url需要role1和role2的权限

     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);
    	//需要满足指定的所有roles才返回true
        return subject.hasAllRoles(roles);
    }
    
  9. [ssl]SslFilter extends PortFilter-针对https协议的指定端口Filter类,同PortFilter 调用示例:ssl[443]

    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    	//多个必须为https协议的条件判断
        return super.isAccessAllowed(request, response, mappedValue) && request.isSecure();
    }
    
  10. [user]UserFilter extends AccessControlFilter-用户认证Filter类

    • 复写isAccessAllowed()方法
    //对login页面请求以及当前用户已存在不拦截,类似于session存在用户属性
    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;
        }
    }
    
    • 复写onAccessDenied()方法
    //直接跳转至login登录页面
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        saveRequestAndRedirectToLogin(request, response);
        return false;
    }
    

继承AdviceFilter抽象类

  1. [logout]LogoutFilter extends AdviceFilter-登录退出Filter类
    只复写了父类的一个方法preHandler(),代码清单如下
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(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;
    }
    

下节预告

Spring-shiro源码陶冶-AuthorizingRealm用户认证以及授权

posted @ 2017-04-28 21:50  南柯问天  阅读(771)  评论(0编辑  收藏  举报