首先Spring Security的认证功能是依赖Filter实现的,当然在认证功能基础上还提供了一些安全的验证等都是依赖Filter来实现完成的,如下截图Spring Security提供了13个功能Filter,并且是按照如下顺序依次执行的。当然配合Spring web的Filter注入实现,Spring Security提供了另外一个Filter的实现类FilterChainProxy,其对外包装了以下13个Filter,其实13个Filter是在接口SecurityFilterChain的实现类DefaultSecurityFilterChain中通过List保存的。

springsecurity 只针对mobile的filter spring security filter_ide

接下来我们依次介绍一下这个13个Filter。

1、WebAsyncManagerIntegrationFilter

接下来我们依次介绍一下这个13个Filter。

1、WebAsyncManagerIntegrationFilter

public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {
     private static final Object CALLABLE_INTERCEPTOR_KEY = new Object();
  
     @Override
     protected void doFilterInternal(HttpServletRequest request,
             HttpServletResponse response, FilterChain filterChain)
             throws ServletException, IOException {
         WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  
         SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
                 .getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
         if (securityProcessingInterceptor == null) {
             asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
                     new SecurityContextCallableProcessingInterceptor());
         }
  
         filterChain.doFilter(request, response);
     }
 }


根据请求封装获取WebAsyncManager
从WebAsyncManager获取/注册SecurityContextCallableProcessingInterceptor
2、SecurityContextPersistenceFilter
两个主要职责:请求来临时,创建SecurityContext安全上下文信息,请求结束时清空SecurityContextHolder。

public class SecurityContextPersistenceFilter extends GenericFilterBean {
  
     static final String FILTER_APPLIED = "__spring_security_scpf_applied";
  
     private SecurityContextRepository repo;
  
     private boolean forceEagerSessionCreation = false;
  
     public SecurityContextPersistenceFilter() {
         this(new HttpSessionSecurityContextRepository());
     }
  
     public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
         this.repo = repo;
     }
  
     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
             throws IOException, ServletException {
         HttpServletRequest request = (HttpServletRequest) req;
         HttpServletResponse response = (HttpServletResponse) res;
         
         //判断是否已经处理过
         if (request.getAttribute(FILTER_APPLIED) != null) {
             // ensure that filter is only applied once per request
             chain.doFilter(request, response);
             return;
         }
  
         final boolean debug = logger.isDebugEnabled();
  
         request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
  
         if (forceEagerSessionCreation) {
             HttpSession session = request.getSession();
  
             if (debug && session.isNew()) {
                 logger.debug("Eagerly created session: " + session.getId());
             }
         }
  
         HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
                 response);
         //获取SecurityContext
         SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
  
         try {
             SecurityContextHolder.setContext(contextBeforeChainExecution);
  
             chain.doFilter(holder.getRequest(), holder.getResponse());
  
         }
         finally {
             //结束后清理SecurityContext
             SecurityContext contextAfterChainExecution = SecurityContextHolder
                     .getContext();
             // Crucial removal of SecurityContextHolder contents - do this before anything
             // else.
             SecurityContextHolder.clearContext();
             repo.saveContext(contextAfterChainExecution, holder.getRequest(),
                     holder.getResponse());
             request.removeAttribute(FILTER_APPLIED);
  
             if (debug) {
                 logger.debug("SecurityContextHolder now cleared, as request processing completed");
             }
         }
     }
  
     public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
         this.forceEagerSessionCreation = forceEagerSessionCreation;
     }
 }


3、HeaderWriterFilter
用来给http响应添加一些Header,比如X-Frame-Options, X-XSS-Protection*,X-Content-Type-Options.

public class HeaderWriterFilter extends OncePerRequestFilter {
  
     /**
      * Collection of {@link HeaderWriter} instances to write out the headers to the
      * response.
      */
     private final List<HeaderWriter> headerWriters;
  
     /**
      * Creates a new instance.
      *
      * @param headerWriters the {@link HeaderWriter} instances to write out headers to the
      * {@link HttpServletResponse}.
      */
     public HeaderWriterFilter(List<HeaderWriter> headerWriters) {
         Assert.notEmpty(headerWriters, "headerWriters cannot be null or empty");
         this.headerWriters = headerWriters;
     }
  
     @Override
     protected void doFilterInternal(HttpServletRequest request,
             HttpServletResponse response, FilterChain filterChain)
                     throws ServletException, IOException {
  
         HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
                 response, this.headerWriters);
         try {
             filterChain.doFilter(request, headerWriterResponse);
         }
         finally {
             headerWriterResponse.writeHeaders();
         }
     }
  
     static class HeaderWriterResponse extends OnCommittedResponseWrapper {
         private final HttpServletRequest request;
         private final List<HeaderWriter> headerWriters;
  
         HeaderWriterResponse(HttpServletRequest request, HttpServletResponse response,
                 List<HeaderWriter> headerWriters) {
             super(response);
             this.request = request;
             this.headerWriters = headerWriters;
         }
  
         /*
          * (non-Javadoc)
          *
          * @see org.springframework.security.web.util.OnCommittedResponseWrapper#
          * onResponseCommitted()
          */
         @Override
         protected void onResponseCommitted() {
             writeHeaders();
             this.disableOnResponseCommitted();
         }
  
         protected void writeHeaders() {
             if (isDisableOnResponseCommitted()) {
                 return;
             }
             for (HeaderWriter headerWriter : this.headerWriters) {
                 headerWriter.writeHeaders(this.request, getHttpResponse());
             }
         }
  
         private HttpServletResponse getHttpResponse() {
             return (HttpServletResponse) getResponse();
         }
     }
 }


4、LogoutFilter
退出拦截器,退出的简单操作就是删除Session,根据Spring Security初始化配置的退出地址来匹配请求。

public class LogoutFilter extends GenericFilterBean {
  
     // ~ Instance fields
     // ================================================================================================
  
     private RequestMatcher logoutRequestMatcher;
  
     private final List<LogoutHandler> handlers;
     private final LogoutSuccessHandler logoutSuccessHandler;
  
     // ~ Constructors
     // ===================================================================================================
  
     /**
      * Constructor which takes a <tt>LogoutSuccessHandler</tt> instance to determine the
      * target destination after logging out. The list of <tt>LogoutHandler</tt>s are
      * intended to perform the actual logout functionality (such as clearing the security
      * context, invalidating the session, etc.).
      */
     public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler,
             LogoutHandler... handlers) {
         Assert.notEmpty(handlers, "LogoutHandlers are required");
         this.handlers = Arrays.asList(handlers);
         Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null");
         this.logoutSuccessHandler = logoutSuccessHandler;
         setFilterProcessesUrl("/logout");
     }
  
     public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) {
         Assert.notEmpty(handlers, "LogoutHandlers are required");
         this.handlers = Arrays.asList(handlers);
         Assert.isTrue(
                 !StringUtils.hasLength(logoutSuccessUrl)
                         || UrlUtils.isValidRedirectUrl(logoutSuccessUrl),
                 logoutSuccessUrl + " isn't a valid redirect URL");
         SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
         if (StringUtils.hasText(logoutSuccessUrl)) {
             urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
         }
         logoutSuccessHandler = urlLogoutSuccessHandler;
         setFilterProcessesUrl("/logout");
     }
  
     // ~ Methods
     // ========================================================================================================
  
     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
             throws IOException, ServletException {
         HttpServletRequest request = (HttpServletRequest) req;
         HttpServletResponse response = (HttpServletResponse) res;
         //判断是不是退出请求
         if (requiresLogout(request, response)) {
             Authentication auth = SecurityContextHolder.getContext().getAuthentication();
  
             if (logger.isDebugEnabled()) {
                 logger.debug("Logging out user '" + auth
                         + "' and transferring to logout destination");
             }
  
             for (LogoutHandler handler : handlers) {
                 //在logoutHandler中进行删除session操作
                 handler.logout(request, response, auth);
             }
  
             logoutSuccessHandler.onLogoutSuccess(request, response, auth);
  
             return;
         }
  
         chain.doFilter(request, response);
     }
  
     /**
      * Allow subclasses to modify when a logout should take place.
      *
      * @param request the request
      * @param response the response
      *
      * @return <code>true</code> if logout should occur, <code>false</code> otherwise
      */
     protected boolean requiresLogout(HttpServletRequest request,
             HttpServletResponse response) {
         return logoutRequestMatcher.matches(request);
     }
  
     public void setLogoutRequestMatcher(RequestMatcher logoutRequestMatcher) {
         Assert.notNull(logoutRequestMatcher, "logoutRequestMatcher cannot be null");
         this.logoutRequestMatcher = logoutRequestMatcher;
     }
  
     public void setFilterProcessesUrl(String filterProcessesUrl) {
         this.logoutRequestMatcher = new AntPathRequestMatcher(filterProcessesUrl);
     }
 }


当判断请求是退出时,会调用LogoutHandler的logout删除session,具体实现在SecurityContextLogoutHandler的logout方法中。

public void logout(HttpServletRequest request, HttpServletResponse response,
             Authentication authentication) {
         Assert.notNull(request, "HttpServletRequest required");
         if (invalidateHttpSession) {
             //设置session无效
             HttpSession session = request.getSession(false);
             if (session != null) {
                 logger.debug("Invalidating session: " + session.getId());
                 session.invalidate();
             }
         }
  
         if (clearAuthentication) {
             SecurityContext context = SecurityContextHolder.getContext();
             context.setAuthentication(null);
         }
         //清理信息
         SecurityContextHolder.clearContext();
     }


5、UsernamePasswordAuthenticationFilter
        用户名和密码校验Filter,是特别重要的一个Filter,我们会在用户登录验证博客中专门进行分析学习一下,当然这个Filter只会对配置的登录请求/login进行业务处理,其他请求不做任何业务处理,其处理逻辑在父类AbstractAuthenticationProcessingFilter中。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
             throws IOException, ServletException {
  
         HttpServletRequest request = (HttpServletRequest) req;
         HttpServletResponse response = (HttpServletResponse) res;
         //首先判断是不是登录请求,不是登录请求则直接跳过
         if (!requiresAuthentication(request, response)) {
             chain.doFilter(request, response);
  
             return;
         }
         //是登录请求进行校验工作
         //省略部分代码
         authResult = attemptAuthentication(request, response);
         //省略部分代码
         
     }


6、DefaultLoginPageGeneratingFilter
         当是登录地址请求,登录失败请求或者登出请求则跳转到登录页面。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
             throws IOException, ServletException {
         HttpServletRequest request = (HttpServletRequest) req;
         HttpServletResponse response = (HttpServletResponse) res;
  
         boolean loginError = isErrorPage(request);
         boolean logoutSuccess = isLogoutSuccess(request);
         //登录请求,登录错误请求或者登出请求则返回登录页面
         if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
             String loginPageHtml = generateLoginPageHtml(request, loginError,
                     logoutSuccess);
             response.setContentType("text/html;charset=UTF-8");
             response.setContentLength(loginPageHtml.length());
             response.getWriter().write(loginPageHtml);
  
             return;
         }
  
         chain.doFilter(request, response);
     }


7、BasicAuthenticationFilter
        处理BASIC authentication认证方式,简单理解和UsernamePasswordAuthenticationFilter类似,不过UsernamePasswordAuthenticationFilter是通过表单提交的,而Authorization认证方式是将用户名密码数据添加到请求header中罢了。

@Override
     protected void doFilterInternal(HttpServletRequest request,
             HttpServletResponse response, FilterChain chain)
                     throws IOException, ServletException {
         final boolean debug = this.logger.isDebugEnabled();
         //请求头中获取Authorization
         String header = request.getHeader("Authorization");
         //如果不存在或者不是以Basic开头直接跳过处理
         if (header == null || !header.startsWith("Basic ")) {
             chain.doFilter(request, response);
             return;
         }
         //从header中获取相关用户名密码数据进行验证
         try {
             String[] tokens = extractAndDecodeHeader(header, request);
             assert tokens.length == 2;
  
             String username = tokens[0];
  
             if (debug) {
                 this.logger
                         .debug("Basic Authentication Authorization header found for user '"
                                 + username + "'");
             }
  
             if (authenticationIsRequired(username)) {
                 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                         username, tokens[1]);
                 authRequest.setDetails(
                         this.authenticationDetailsSource.buildDetails(request));
                 Authentication authResult = this.authenticationManager
                         .authenticate(authRequest);
  
                 if (debug) {
                     this.logger.debug("Authentication success: " + authResult);
                 }
  
                 SecurityContextHolder.getContext().setAuthentication(authResult);
  
                 this.rememberMeServices.loginSuccess(request, response, authResult);
  
                 onSuccessfulAuthentication(request, response, authResult);
             }
  
         }
         catch (AuthenticationException failed) {
             SecurityContextHolder.clearContext();
  
             if (debug) {
                 this.logger.debug("Authentication request for failed: " + failed);
             }
  
             this.rememberMeServices.loginFail(request, response);
  
             onUnsuccessfulAuthentication(request, response, failed);
  
             if (this.ignoreFailure) {
                 chain.doFilter(request, response);
             }
             else {
                 this.authenticationEntryPoint.commence(request, response, failed);
             }
  
             return;
         }
  
         chain.doFilter(request, response);
     }


8、RequestCacheAwareFilter 
内部维护了一个RequestCache,用于缓存request请求

public class RequestCacheAwareFilter extends GenericFilterBean {
  
     private RequestCache requestCache;
  
     public RequestCacheAwareFilter() {
         this(new HttpSessionRequestCache());
     }
  
     public RequestCacheAwareFilter(RequestCache requestCache) {
         Assert.notNull(requestCache, "requestCache cannot be null");
         this.requestCache = requestCache;
     }
  
     public void doFilter(ServletRequest request, ServletResponse response,
             FilterChain chain) throws IOException, ServletException {
  
         HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
                 (HttpServletRequest) request, (HttpServletResponse) response);
  
         chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
                 response);
     }
  
 }


9、SecurityContextHolderAwareRequestFilter 
        此过滤器对ServletRequest进行了一次包装,使得request具有更加丰富的API

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
             throws IOException, ServletException {
         chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
                 (HttpServletResponse) res), res);
     }


10、AnonymousAuthenticationFilter
           匿名身份过滤器,这个过滤器个人认为很重要,需要将它与UsernamePasswordAuthenticationFilter 放在一起比较理解,spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
             throws IOException, ServletException {
  
         if (SecurityContextHolder.getContext().getAuthentication() == null) {
             //创建一个匿名身份
             SecurityContextHolder.getContext().setAuthentication(
                     createAuthentication((HttpServletRequest) req));
  
             if (logger.isDebugEnabled()) {
                 logger.debug("Populated SecurityContextHolder with anonymous token: '"
                         + SecurityContextHolder.getContext().getAuthentication() + "'");
             }
         }
         else {
             if (logger.isDebugEnabled()) {
                 logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
                         + SecurityContextHolder.getContext().getAuthentication() + "'");
             }
         }
  
         chain.doFilter(req, res);
     }
  
     protected Authentication createAuthentication(HttpServletRequest request) {
         AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
                 principal, authorities);
         auth.setDetails(authenticationDetailsSource.buildDetails(request));
  
         return auth;
     }


11、SessionManagementFilter
           和session相关的过滤器,内部维护了一个SessionAuthenticationStrategy,两者组合使用,常用来防止session-fixation protection attack,以及限制同一用户开启多个会话的数量

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
             throws IOException, ServletException {
         HttpServletRequest request = (HttpServletRequest) req;
         HttpServletResponse response = (HttpServletResponse) res;
  
         if (request.getAttribute(FILTER_APPLIED) != null) {
             chain.doFilter(request, response);
             return;
         }
  
         request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
  
         if (!securityContextRepository.containsContext(request)) {
             Authentication authentication = SecurityContextHolder.getContext()
                     .getAuthentication();
  
             if (authentication != null && !trustResolver.isAnonymous(authentication)) {
                 // The user has been authenticated during the current request, so call the
                 // session strategy
                 try {
                     sessionAuthenticationStrategy.onAuthentication(authentication,
                             request, response);
                 }
                 catch (SessionAuthenticationException e) {
                     // The session strategy can reject the authentication
                     logger.debug(
                             "SessionAuthenticationStrategy rejected the authentication object",
                             e);
                     SecurityContextHolder.clearContext();
                     failureHandler.onAuthenticationFailure(request, response, e);
  
                     return;
                 }
                 // Eagerly save the security context to make it available for any possible
                 // re-entrant
                 // requests which may occur before the current request completes.
                 // SEC-1396.
                 securityContextRepository.saveContext(SecurityContextHolder.getContext(),
                         request, response);
             }
             else {
                 // No security context or authentication present. Check for a session
                 // timeout
                 if (request.getRequestedSessionId() != null
                         && !request.isRequestedSessionIdValid()) {
                     if (logger.isDebugEnabled()) {
                         logger.debug("Requested session ID "
                                 + request.getRequestedSessionId() + " is invalid.");
                     }
  
                     if (invalidSessionStrategy != null) {
                         invalidSessionStrategy
                                 .onInvalidSessionDetected(request, response);
                         return;
                     }
                 }
             }
         }
  
         chain.doFilter(request, response);
     }


12、ExceptionTranslationFilter
        ExceptionTranslationFilter异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异常,将其转化,顾名思义,转化以意味本身并不处理。一般其只处理两大类异常:AccessDeniedException访问异常和AuthenticationException认证异常。这个过滤器非常重要,因为它将Java中的异常和HTTP的响应连接在了一起,这样在处理异常时,我们不用考虑密码错误该跳到什么页面,账号锁定该如何,只需要关注自己的业务逻辑,抛出相应的异常便可。如果该过滤器检测到AuthenticationException,则将会交给内部的AuthenticationEntryPoint去处理,如果检测到AccessDeniedException,需要先判断当前用户是不是匿名用户,如果是匿名访问,则和前面一样运行AuthenticationEntryPoint,否则会委托给AccessDeniedHandler去处理,而AccessDeniedHandler的默认实现,是AccessDeniedHandlerImpl。所以ExceptionTranslationFilter内部的AuthenticationEntryPoint是至关重要的,顾名思义:认证的入口点。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
             throws IOException, ServletException {
         HttpServletRequest request = (HttpServletRequest) req;
         HttpServletResponse response = (HttpServletResponse) res;
  
         try {
             chain.doFilter(request, response);
  
             logger.debug("Chain processed normally");
         }
         catch (IOException ex) {
             throw ex;
         }
         //捕捉一些异常处理,身份校验判断错误会抛出异常,由这里处理
         catch (Exception ex) {
             // Try to extract a SpringSecurityException from the stacktrace
             Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
             RuntimeException ase = (AuthenticationException) throwableAnalyzer
                     .getFirstThrowableOfType(AuthenticationException.class, causeChain);
  
             if (ase == null) {
                 ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
                         AccessDeniedException.class, causeChain);
             }
  
             if (ase != null) {
                 handleSpringSecurityException(request, response, chain, ase);
             }
             else {
                 // Rethrow ServletExceptions and RuntimeExceptions as-is
                 if (ex instanceof ServletException) {
                     throw (ServletException) ex;
                 }
                 else if (ex instanceof RuntimeException) {
                     throw (RuntimeException) ex;
                 }
  
                 // Wrap other Exceptions. This shouldn't actually happen
                 // as we've already covered all the possibilities for doFilter
                 throw new RuntimeException(ex);
             }
         }
     }


13、FilterSecurityInterceptor
        个人理解任务这个拦截器是Spring Security中最重要的,首先这个拦截器就是进行角色相关的处理,也是登录结果是否成功最后一个判断的拦截器,针对不需要认证和需要认证的请求会进行不同的验证处理,同样在登录博客中会对其做的功能进行详细的说明。

public void doFilter(ServletRequest request, ServletResponse response,
             FilterChain chain) throws IOException, ServletException {
         FilterInvocation fi = new FilterInvocation(request, response, chain);
  
         invoke(fi);
     }
  
 public void invoke(FilterInvocation fi) throws IOException, ServletException {
         if ((fi.getRequest() != null)
                 && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
                 && observeOncePerRequest) {
             // filter already applied to this request and user wants us to observe
             // once-per-request handling, so don't re-do security checking
             fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
         }
         else {
             // first time this request being called, so perform security checking
             if (fi.getRequest() != null) {
                 fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
             }
             //在执行请求调用之前进行验证处理
             InterceptorStatusToken token = super.beforeInvocation(fi);
  
             try {
                 fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
             }
             finally {
                 super.finallyInvocation(token);
             }
  
             super.afterInvocation(token, null);
         }
     }