zoukankan      html  css  js  c++  java
  • 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用户认证以及授权

  • 相关阅读:
    【VirtualBox】共享文件夹失效问题
    【Ubuntu】全局代理
    phpStudy(lnmp)集成环境安装
    MemcacheQ 的安装与使用
    Windows 64位下安装Redis详细教程
    http与https的区别
    cookie 和session 的区别详解
    setcookie各个参数详解
    MySQL 数据备份与还原
    linux命令行下导出导入.sql文件
  • 原文地址:https://www.cnblogs.com/question-sky/p/6783449.html
Copyright © 2011-2022 走看看