spring security 源码分析: 过滤器

转载自:http://feiyan35488.iteye.com/blog/898348

首先 请求进入 FilterChainProxy 这个类

 

   FilterChainProxy.java

Java代码   收藏代码
  1. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)  
  2.            throws IOException, ServletException {  
  3.   
  4.        FilterInvocation fi = new FilterInvocation(request, response, chain);  
  5.        List<Filter> filters = getFilters(fi.getRequestUrl());  
  6.   
  7.        if (filters == null || filters.size() == 0) {  
  8.            if (logger.isDebugEnabled()) {  
  9.                logger.debug(fi.getRequestUrl() +  
  10.                        filters == null ? " has no matching filters" : " has an empty filter list");  
  11.            }  
  12.   
  13.            chain.doFilter(request, response);  
  14.   
  15.            return;  
  16.        }  
  17.   
  18.        VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi, filters);  
  19.        virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());  
  20.    }  
  21. ublic List<Filter> getFilters(String url)  {  
  22.        if (stripQueryStringFromUrls) {  
  23.            // String query string - see SEC-953  
  24.            int firstQuestionMarkIndex = url.indexOf("?");  
  25.   
  26.            if (firstQuestionMarkIndex != -1) {  
  27.                url = url.substring(0, firstQuestionMarkIndex);  
  28.            }  
  29.        }  
  30.   
  31.        for (Map.Entry<Object, List<Filter>> entry : filterChainMap.entrySet()) {  
  32.            Object path = entry.getKey();  
  33.   
  34.            if (matcher.requiresLowerCaseUrl()) {  
  35.                url = url.toLowerCase();  
  36.   
  37.                if (logger.isDebugEnabled()) {  
  38.                    logger.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'");  
  39.                }  
  40.            }  
  41.   
  42.            boolean matched = matcher.pathMatchesUrl(path, url);  
  43.   
  44.            if (logger.isDebugEnabled()) {  
  45.                logger.debug("Candidate is: '" + url + "'; pattern is " + path + "; matched=" + matched);  
  46.            }  
  47.   
  48.            if (matched) {  
  49.                return entry.getValue();  
  50.            }  
  51.        }  
  52.   
  53.        return null;  
  54.    }  

 

   可以看出, FilterInvocation 是见 request 和 response ,chain 只是进行了封装, 然后根据 url 来判断这个请求是否需要进行拦截, 这里 getFilter() 方法是查询的 intercepter-url 中配置的 内容。(这里具体的内容在下面)

   接下来就是执行所有的List<Filter> 。执行完所有的List<Filter>之后会继续执行容器的filterChain

   VirtualFilterChain.java 这是 FilterChainProxy 的内部类

Java代码   收藏代码
  1. public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {  
  2.           <span style="color: #ff0000;">  if (currentPosition == additionalFilters.size()) {  
  3.                </span> if (logger.isDebugEnabled()) {  
  4.                     logger.debug(fi.getRequestUrl()  
  5.                         + " reached end of additional filter chain; proceeding with original chain");  
  6.                 }  
  7. <span style="color: #ff0000;">  
  8.                 fi.getChain().doFilter(request, response);</span>  
  9.             } else {  
  10.                 currentPosition++;  
  11.   
  12.                 Filter nextFilter = additionalFilters.get(currentPosition - 1);  
  13.   
  14.                 if (logger.isDebugEnabled()) {  
  15.                     logger.debug(fi.getRequestUrl() + " at position " + currentPosition + " of "  
  16.                         + additionalFilters.size() + " in additional filter chain; firing Filter: '"  
  17.                         + nextFilter + "'");  
  18.                 }  
  19.   
  20.                nextFilter.doFilter(request, response, this);  
  21.             }  
  22.         }  
 

   下面先按顺序分析各Filter的作用 (security默认添加的filterChain,共11个 还有大概4,5个没有涉及到,以后涉及到再进行添加)

      1.org.springframework.security.web.context.SecurityContextPersistenceFilter

          (2.0中是这个HttpSessionContextIntegrationFilter

        从这个类所在的包路径  context,大致知道这个类 只处理 上下文 

Java代码   收藏代码
  1. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)            
  2.             throws IOException, ServletException {  
  3.        HttpServletRequest request = (HttpServletRequest) req;  
  4.        HttpServletResponse response = (HttpServletResponse) res;  
  5.   
  6.        if (request.getAttribute(FILTER_APPLIED) != null) {  
  7.            // ensure that filter is only applied once per request  
  8.            chain.doFilter(request, response);  
  9.            return;  
  10.        }  
  11.   
  12.        final boolean debug = logger.isDebugEnabled();  
  13.   
  14.        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);  
  15.   
  16.        if (forceEagerSessionCreation) {  
  17.            HttpSession session = request.getSession();  
  18.   
  19.            if (debug && session.isNew()) {  
  20.                logger.debug("Eagerly created session: " + session.getId());  
  21.            }  
  22.        }  
  23.   
  24.        HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);  
  25.        SecurityContext contextBeforeChainExecution = repo.loadContext(holder);  
  26.   
  27.        try {  
  28.            SecurityContextHolder.setContext(contextBeforeChainExecution);  
  29.   
  30.            chain.doFilter(holder.getRequest(), holder.getResponse());  
  31.   
  32.        } finally {  
  33.            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();  
  34.            // Crucial removal of SecurityContextHolder contents - do this before anything else.  
  35.            SecurityContextHolder.clearContext();  
  36.            repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());  
  37.            request.removeAttribute(FILTER_APPLIED);  
  38.   
  39.            if (debug) {  
  40.                logger.debug("SecurityContextHolder now cleared, as request processing completed");  
  41.            }  
  42.        }  
  43.    }  
 

    从代码看: 在一次request中只执行一次,并生成 SecurityContext(从session中读取,如果session中没有就创建一个新的),注册到 SecurityContextHolder中,当请求执行完后,清除该SecurityContext 和request中的 filter_applied 属性。在源码中类注释提到: 这个类 一次请求中只能执行一次,并且它应该在 任何认证过程之前 执行。

   ============================华丽丽的分割线===========================

2,org.springframework.security.web.authentication.logout.LogoutFilter

Java代码   收藏代码
  1. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)  
  2.             throws IOException, ServletException {  
  3.         HttpServletRequest request = (HttpServletRequest) req;  
  4.         HttpServletResponse response = (HttpServletResponse) res;  
  5.   
  6.         if (requiresLogout(request, response)) {  
  7.             Authentication auth = SecurityContextHolder.getContext().getAuthentication();  
  8.   
  9.             if (logger.isDebugEnabled()) {  
  10.                 logger.debug("Logging out user '" + auth + "' and transferring to logout destination");  
  11.             }  
  12.   
  13.             for (LogoutHandler handler : handlers) {  
  14.                 handler.logout(request, response, auth);  
  15.             }  
  16.   
  17.             logoutSuccessHandler.onLogoutSuccess(request, response, auth);  
  18.   
  19.             return;  
  20.         }  
  21.   
  22.         chain.doFilter(request, response);  
  23.     }  
  24.   
  25.     /** 
  26.      * Allow subclasses to modify when a logout should take place. 
  27.      * 
  28.      * @param request the request 
  29.      * @param response the response 
  30.      * 
  31.      * @return <code>true</code> if logout should occur, <code>false</code> otherwise 
  32.      */  
  33.     protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) {  
  34.         String uri = request.getRequestURI();  
  35.         int pathParamIndex = uri.indexOf(';');  
  36.   
  37.         if (pathParamIndex > 0) {  
  38.             // strip everything from the first semi-colon  
  39.             uri = uri.substring(0, pathParamIndex);  
  40.         }  
  41.   
  42.         int queryParamIndex = uri.indexOf('?');  
  43.   
  44.         if (queryParamIndex > 0) {  
  45.             // strip everything from the first question mark  
  46.             uri = uri.substring(0, queryParamIndex);  
  47.         }  
  48.   
  49.         if ("".equals(request.getContextPath())) {  
  50.             return uri.endsWith(filterProcessesUrl);  
  51.         }  
  52.   
  53.         return uri.endsWith(request.getContextPath() + filterProcessesUrl);  
  54.     }  

    这个处理比较简单, 只是检查是否为 登出地址,是的话就退出然后返回,不是的话就进行下一个filter。

   这个判断是否为登录地址我感觉很不正常,他判断是否以 logout_url 结尾,直接判断是否相等才对啊。

  而且他根据 contextpath 是否为“” ,其实不用的,直接判断 contextPath+logout_url 即可。

Java代码   收藏代码
  1. org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter .java  

Java代码   收藏代码
  1. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)  
  2.             throws IOException, ServletException {  
  3.   
  4.         HttpServletRequest request = (HttpServletRequest) req;  
  5.         HttpServletResponse response = (HttpServletResponse) res;  
  6.   
  7.         if (!requiresAuthentication(request, response)) {  
  8.             chain.doFilter(request, response);  
  9.   
  10.             return;  
  11.         }  
  12.   
  13.         if (logger.isDebugEnabled()) {  
  14.             logger.debug("Request is to process authentication");  
  15.         }  
  16.   
  17.         Authentication authResult;  
  18.   
  19.         try {  
  20.             authResult = attemptAuthentication(request, response);  
  21.             if (authResult == null) {  
  22.                 // return immediately as subclass has indicated that it hasn't completed authentication  
  23.                 return;  
  24.             }  
  25.             sessionStrategy.onAuthentication(authResult, request, response);  
  26.         }  
  27.         catch (AuthenticationException failed) {  
  28.             // Authentication failed  
  29.             unsuccessfulAuthentication(request, response, failed);  
  30.   
  31.             return;  
  32.         }  
  33.   
  34.         // Authentication success  
  35.         if (continueChainBeforeSuccessfulAuthentication) {  
  36.             chain.doFilter(request, response);  
  37.         }  
  38.   
  39.         successfulAuthentication(request, response, authResult);  
  40.     }  
  41.  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {  
  42.         if (postOnly && !request.getMethod().equals("POST")) {  
  43.             throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());  
  44.         }  
  45.   
  46.         String username = obtainUsername(request);  
  47.         String password = obtainPassword(request);  
  48.   
  49.         if (username == null) {  
  50.             username = "";  
  51.         }  
  52.   
  53.         if (password == null) {  
  54.             password = "";  
  55.         }  
  56.   
  57.         username = username.trim();  
  58.   
  59.         UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);  
  60.   
  61.         // Place the last username attempted into HttpSession for views  
  62.         HttpSession session = request.getSession(false);  
  63.   
  64.         if (session != null || getAllowSessionCreation()) {  
  65.             request.getSession().setAttribute(SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(username));  
  66.         }  
  67.   
  68.         // Allow subclasses to set the "details" property  
  69.         setDetails(request, authRequest);  
  70.   
  71.         return this.getAuthenticationManager().authenticate(authRequest);  
  72.     }  
Java代码   收藏代码
  1. public Authentication doAuthentication(Authentication authentication) throws AuthenticationException {  
  2.     Class<? extends Authentication> toTest = authentication.getClass();  
  3.     AuthenticationException lastException = null;  
  4.     Authentication result = null;  
  5.   
  6.     for (AuthenticationProvider provider : getProviders()) {  
  7.         if (!provider.supports(toTest)) {  
  8.             continue;  
  9.         }  
  10.   
  11.         logger.debug("Authentication attempt using " + provider.getClass().getName());  
  12.   
  13.         try {  
  14.             result = provider.authenticate(authentication);  
  15.   
  16.             if (result != null) {  
  17.                 copyDetails(authentication, result);  
  18.                 break;  
  19.             }  
  20.         } catch (AccountStatusException e) {  
  21.             // SEC-546: Avoid polling additional providers if auth failure is due to invalid account status  
  22.             eventPublisher.publishAuthenticationFailure(e, authentication);  
  23.             throw e;  
  24.         } catch (AuthenticationException e) {  
  25.             lastException = e;  
  26.         }  
  27.     }  
  28.   
  29.     if (result == null && parent != null) {  
  30.         // Allow the parent to try.  
  31.         try {  
  32.             result = parent.authenticate(authentication);  
  33.         } catch (ProviderNotFoundException e) {  
  34.             // ignore as we will throw below if no other exception occurred prior to calling parent and the parent  
  35.             // may throw ProviderNotFound even though a provider in the child already handled the request  
  36.         } catch (AuthenticationException e) {  
  37.             lastException = e;  
  38.         }  
  39.     }  
  40.   
  41.     if (result != null) {  
  42.         if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {  
  43.             // Authentication is complete. Remove credentials and other secret data from authentication  
  44.             ((CredentialsContainer)result).eraseCredentials();  
  45.         }  
  46.   
  47.         eventPublisher.publishAuthenticationSuccess(result);  
  48.         return result;  
  49.     }  
  50.   
  51.     // Parent was null, or didn't authenticate (or throw an exception).  
  52.   
  53.     if (lastException == null) {  
  54.         lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",  
  55.                     new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}"));  
  56.     }  
  57.   
  58.     eventPublisher.publishAuthenticationFailure(lastException, authentication);  
  59.   
  60.     throw lastException;  
  61. }  
 

   过程: 判断是否 为登录地址,是则进行认证,否则 继续下一个filter

   认证过程: 取得 username,password, 调用 AuthenticationManager.authenticate(){
             然后调用 所有的AuthenticationProvider 进行认证,有一个认证通过即可通过。在AuthenticationProvider中调用 配置的 UserDetailsService 的 loadUserByUserame() 得到 UserDetails,  当第一次从数据库取得后,会将UserDetails保存到 Cache中,这给权限分配的 及时性带来了困难,不过它专门提供了一个filter来进行 热部署权限

      
    }
    还有一点,这个filter中判断 "j_spring_security_check"这个地址也是以 endWith来匹配的,感觉不对。

   回家了,晚上继续

 

===========================华丽丽的分割线=================================

 

org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter

org.springframework.security.web.authentication.www.BasicAuthenticationFilter

org.springframework.security.web.savedrequest.RequestCacheAwareFilter

org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter

org.springframework.security.web.authentication.AnonymousAuthenticationFilter

org.springframework.security.web.session.SessionManagementFilter

org.springframework.security.web.access.ExceptionTranslationFilter

org.springframework.security.web.access.intercept.FilterSecurityInterceptor


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值