結(jié)合源碼學(xué)習(xí)使用SpringSecurity

一缀匕、 關(guān)鍵詞

Authentication:鑒權(quán),我理解為身份認證碰逸,是權(quán)限驗證中的一個特殊的分支乡小。
Authorization:授權(quán),表示為權(quán)限驗證饵史。
AuthenticationProvider:認證處理满钟,對每一個支持的認證對象的身份進行認證。
AuthenticationManager:認證管理器胳喷,管理多個AuthenticationProvider湃番,實現(xiàn)允許多個認證處理去執(zhí)行不同來源的身份認證。
AccessDecisionManager:驗證管理器吭露,主要是對Security表達式(表達式數(shù)量可以是多個)進行權(quán)限驗證處理吠撮。例如:authenticated()、hasRole()讲竿、hasAuthority()泥兰、access(表達式)等方法進行權(quán)限的驗證。其中authenticated()表示對用戶是否擁有驗明的身份權(quán)题禀,即為身份認證鞋诗。
權(quán)限通過:AccessDecisionManager管理有三個實現(xiàn)類,分別對應(yīng)三種權(quán)限驗證通過的方式迈嘹。權(quán)限驗證通過是通過類似投票方式?jīng)Q定削彬,有三種投票機制
? ACCESS_GRANTED:贊成票
? ACCESS_ABSTAIN:棄權(quán)票
? ACCESS_DENIED:否決票

通過方式 實現(xiàn)類 解釋
非否決通過 AffirmativeBased 1.有贊成票即可通過
2.全部棄權(quán)即可通過
少數(shù)服從多數(shù) ConsensusBased 1.贊成票大于否決票則通過
2.贊成票和否決票相同,allowIfEqualGrantedDeniedDecisions=true則通過
3.全部棄權(quán)秀仲,allowIfEqualGrantedDeniedDecisions=true則通過
一致通過 UnanimousBased 1.有贊成票吃警,沒有反對票則通過
2.全部否決,allowIfAllAbstainDecisions=true則通過

二啄育、 基本流程

?1.從@EnableWebSecurity說起

?源碼如下

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class, // 導(dǎo)入WebSecurity配置酌心,是針對于Web方面的一些注冊
        SpringWebMvcImportSelector.class,
        OAuth2ImportSelector.class })
@EnableGlobalAuthentication // 允許全局定義認證機制,包含一些認證的注冊配置等等
@Configuration
public @interface EnableWebSecurity {

    /**
     * Controls debugging support for Spring Security. Default is false.
     * @return if true, enables debug support with Spring Security
     */
    boolean debug() default false;
}

?在WebSecurityConfiguration類中有setFilterChainProxySecurityConfigurer()方法挑豌,這個方法將會導(dǎo)入關(guān)于SpringSecurity的所有配置器對象(實現(xiàn)WebSecurityConfigurer接口的實例)安券,方法如下:

@Autowired(required = false)
    public void setFilterChainProxySecurityConfigurer(
            ObjectPostProcessor<Object> objectPostProcessor,
            // 在本處需要導(dǎo)入系統(tǒng)中關(guān)于WebSecurityConfigurer配置器
            @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)

?進入@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()方法進行調(diào)試墩崩。如圖

getWebSecurityConfigurers()方法

?從圖中可知,Spring將會注入SpringBootWebSecurityConfiguration對象
?查看SpringBootWebSecurityConfiguration源碼
SpringBootWebSecurityConfiguration

?從圖中可知:

  • 根據(jù)@ConditionalOnClass@ConditionalOnMissingBean這兩個條件來自定義創(chuàng)建WebSecurityConfigurerAdapter對象侯勉,從而實現(xiàn)注入鹦筹。
  • 因此,通常自定義Security配置的話址貌,都是繼承WebSecurityConfigurerAdapter铐拐,并注入到Spring容器中就好。
  • 如果配置多個Security配置器练对,則如圖


配置后運行情況
?2.那么注冊配置器后有什么用呢遍蟋?

?根據(jù)Spring官方文檔中描述如圖


?SpringBoot的自啟動實現(xiàn)三點:

  • 啟用Spring Security的默認配置,該配置將Servlet過濾器創(chuàng)建為名為springSecurityFilterChain的bean螟凭。該bean負責(zé)應(yīng)用程序中的所有安全性(保護應(yīng)用程序URL虚青,驗證提交的用戶名和密碼,重定向到登錄表單等)螺男。
  • 創(chuàng)建一個UserDetailsService bean棒厘,其中包含用戶名user和隨機生成的密碼,該密碼將記錄到控制臺下隧。
  • 對于每個請求奢人,使用Servlet容器向名為springSecurityFilterChain的bean注冊過濾器。
    注冊名為springSecurityFilterChain的過濾器淆院,實際上注冊的是FilterChainProxy對象达传,該對象中保存有兩個WebSecurityConfigurer配置器的配置過濾器鏈。而整個請求的過濾都將通過這一系列過濾器進行過濾迫筑。如圖


?3.查看FilterChainProxy類源碼宪赶,分析其作用
// FilterChainProxy類的doFilter方法
public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {
   // 配置每一個請求只過濾一次。
   boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
   if (clearContext) {
      try {
         request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
         doFilterInternal(request, response, chain);
      }
      finally {
         SecurityContextHolder.clearContext();
         request.removeAttribute(FILTER_APPLIED);
      }
   }
   else {
      doFilterInternal(request, response, chain);
   }
}

?可以看出最終將都將會執(zhí)行doFilterInternal方法脯燃。繼續(xù)查看該方法源碼

private void doFilterInternal(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {

   FirewalledRequest fwRequest = firewall
         .getFirewalledRequest((HttpServletRequest) request);
   HttpServletResponse fwResponse = firewall
         .getFirewalledResponse((HttpServletResponse) response);

   List<Filter> filters = getFilters(fwRequest);

   if (filters == null || filters.size() == 0) {
      if (logger.isDebugEnabled()) {
         logger.debug(UrlUtils.buildRequestUrl(fwRequest)
               + (filters == null ? " has no matching filters"
                     : " has an empty filter list"));
      }

      fwRequest.reset();

      chain.doFilter(fwRequest, fwResponse);

      return;
   }

   // 通過自定義過濾器執(zhí)行鏈來執(zhí)行每一個過濾器
   VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
   vfc.doFilter(fwRequest, fwResponse);
}

?進入List<Filter> filters = getFilters(fwRequest);方法

private List<Filter> getFilters(HttpServletRequest request) {
   for (SecurityFilterChain chain : filterChains) {
      if (chain.matches(request)) {
         return chain.getFilters();
      }
   }
   return null;
}

?該方法表明將從所有的WebSecurityConfiguration配置對象所生成的過濾器鏈集合中找出與當前請求所匹配的過濾器鏈搂妻,并返回過濾器鏈中的所有過濾器。



請求匹配的過濾鏈的所有過濾器
?所有的過濾器如下:
  • 1.org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
  • 2.org.springframework.security.web.context.SecurityContextPersistenceFilter
  • 3.org.springframework.security.web.header.HeaderWriterFilter
  • 4.org.springframework.security.web.csrf.CsrfFilter
  • 5.org.springframework.security.web.authentication.logout.LogoutFilter
  • 6.org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
  • 7.org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
  • 8.org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
  • 9.org.springframework.security.web.authentication.www.BasicAuthenticationFilter
  • 10.org.springframework.security.web.savedrequest.RequestCacheAwareFilter
  • 11.org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
  • 12.org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter
  • 13.org.springframework.security.web.authentication.AnonymousAuthenticationFilter
  • 14.org.springframework.security.web.session.SessionManagementFilter
  • 15.org.springframework.security.web.access.ExceptionTranslationFilter
  • 16.org.springframework.security.web.access.intercept.FilterSecurityInterceptor
  • 17.org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor(注:并未出現(xiàn)在過濾鏈中辕棚,只是可以歸為一類型理解處理)
?4.在WebSecurityConfiguration配置中欲主,過濾器是如何加入到請求過濾鏈中的
默認配置

?自定義配置增加rememberMe()方法,使過濾器鏈中加入RememberMeAuthenticationFilter


自定義配置

?添加全局方法注解逝嚎,會使用代理MethodSecurityInterceptor處理業(yè)務(wù)方法


三扁瓢、 粗略分析每個過濾器

?前提:解釋一下OncePerRequestFilter過濾器,因為有一些過濾器都會繼承OncePerRequestFilter补君,所以在此先看一下該過濾器的作用

?0.OncePerRequestFilter

?源碼如下:

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
        HttpServletRequest httpRequest = (HttpServletRequest)request;
        HttpServletResponse httpResponse = (HttpServletResponse)response;
        // 是否被過濾
        String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
        boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
        if (!this.skipDispatch(httpRequest) && !this.shouldNotFilter(httpRequest)) {
            // 通過此處控制每個請求只被過濾一次
            if (hasAlreadyFilteredAttribute) {
                if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
                    this.doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
                    return;
                }

                filterChain.doFilter(request, response);
            } else {
                // 設(shè)置已經(jīng)被過濾
                request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

                try {
                    // 主要的過濾方法
                    this.doFilterInternal(httpRequest, httpResponse, filterChain);
                } finally {
                    request.removeAttribute(alreadyFilteredAttributeName);
                }
            }
        } else {
            filterChain.doFilter(request, response);
        }

    } else {
        throw new ServletException("OncePerRequestFilter just supports HTTP requests");
    }
}

?作用:大致表現(xiàn)為每一個請求只能被過濾器過濾一次引几。過濾時調(diào)用doFilterInternal方法,因此每一個子類只需要看doFilterInternal方法即可挽铁。

?1. WebAsyncManagerIntegrationFilter

?名稱:Web異步管理器集成過濾器伟桅,繼承OncePerRequestFilter
?源碼:

@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);
    }

?作用:提供SecurityContext和SpringWeb的集成

  • doFilter前:進行SecurityContext和SpringWeb集成
  • doFilter后:無
?2. SecurityContextPersistenceFilter

?名稱:SecurityContext持久化過濾器
?源碼:

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) {
      // 防止重復(fù)過濾 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);
   // 提前創(chuàng)建session,如果SecurityContext使用HttpSession管理敞掘。作用暫時不清楚
   if (forceEagerSessionCreation) {
      HttpSession session = request.getSession();

      if (debug && session.isNew()) {
         logger.debug("Eagerly created session: " + session.getId());
      }
   }

   // 實際上就是講request和response封裝為一個對象,然后傳入SecurityContext持久庫中加載SecurityContext楣铁。
   HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
         response);
   // 從SecurityContext持久庫中加載SecurityContext
   SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

   try {
      // 將加載后的SecurityContext設(shè)置到SecurityContextHolder中玖雁,方便以后使用
      SecurityContextHolder.setContext(contextBeforeChainExecution);

      chain.doFilter(holder.getRequest(), holder.getResponse());

   }
   finally {
      SecurityContext contextAfterChainExecution = SecurityContextHolder
            .getContext();
      // 清空SecurityContextHolder中的SecurityContext
      SecurityContextHolder.clearContext();
      // 將經(jīng)過一系列修改的SecurityContext保存至SecurityContext庫中。
      repo.saveContext(contextAfterChainExecution, holder.getRequest(),
            holder.getResponse());
      request.removeAttribute(FILTER_APPLIED);

      if (debug) {
         logger.debug("SecurityContextHolder now cleared, as request processing completed");
      }
   }
}

?作用:對SecurityContext進行持久化儲存

  • doFilter前:嘗試從SecurityContext持久庫中調(diào)用loadContext方法加載SecurityContext盖腕,并設(shè)置到SecurityContextHolder中方便之后使用
  • doFilter后:
    • 清空SecurityContextHolder中的SecurityContext
    • 將經(jīng)過一系列修改的SecurityContext保存至SecurityContext庫中赫冬。

?其他:關(guān)于SecurityContextRepository的載入和自定義。

  • 加載SecurityContextRepository:調(diào)用http. securityContext()溃列,進入后直接調(diào)用SecurityContextConfigurer的configure(H http)方法劲厌。
public void configure(H http) {

   // 從sharedObject中拿SecurityContextRepository對象
   SecurityContextRepository securityContextRepository = http
         .getSharedObject(SecurityContextRepository.class);
   // 如果沒拿到,則新建一個session管理的SecurityContextRepository對象
   if (securityContextRepository == null) {
      securityContextRepository = new HttpSessionSecurityContextRepository();
   }
   SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
         securityContextRepository);
   // 拿到session管理配置
   SessionManagementConfigurer<?> sessionManagement = http
         .getConfigurer(SessionManagementConfigurer.class);
   // 從session管理配置中拿到創(chuàng)建策略
   SessionCreationPolicy sessionCreationPolicy = sessionManagement == null ? null
         : sessionManagement.getSessionCreationPolicy();
   // 如果是ALWAYS策略哭廉,則允許securityContextFilter提前創(chuàng)建session
   if (SessionCreationPolicy.ALWAYS == sessionCreationPolicy) {
      securityContextFilter.setForceEagerSessionCreation(true);
   }
   // 對securityContextFilter過濾器后置處理
   // 其會調(diào)用http.objectPostProcessor()方法提供的后置處理器。
   securityContextFilter = postProcess(securityContextFilter);
   http.addFilter(securityContextFilter);
}

  • 因此:有兩種自定義SecurityContextRepository的方法相叁。
    • 一是http.setSharedObject(SecurityContextRepository.class, obj)將自定義的庫對象放入到SharedObject中
    • 二是http.securityContext(Customizer<SecurityContextConfigurer<HttpSecurity>> securityContextCustomizer)重新自定義SecurityContextPersistenceFilter過濾器
?3. HeaderWriterFilter

?名稱:請求頭填寫過濾器
?繼承OncePerRequestFilter
?源碼:

    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        if (this.shouldWriteHeadersEagerly) {
            doHeadersBefore(request, response, filterChain);
        } else {
            doHeadersAfter(request, response, filterChain);
        }
    }

?作用:為請求頭添加headers

  • doFilter前:如果屬性shouldWriteHeadersEagerly為true掸绞,則會在過濾前添加請求頭豆瘫。否則,過濾前不會添加。
  • doFilter后:如果屬性shouldWriteHeadersEagerly為false山憨,則會在過濾后添加請求頭。否則肥荔,過濾后不會添加骇两。
  • shouldWriteHeadersEagerly默認為false
?4. CsrfFilter

?名稱:Csrf過濾器,繼承OncePerRequestFilter
?作用:開啟Csrf防御

  • doFilter前:驗證是否csrf安全
  • doFilter后:無
?5. LogoutFilter

?名稱:登出過濾器
?源碼:

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");
            }

            // 登出的處理
            this.handler.logout(request, response, auth);
            // 登出成功后處理
            logoutSuccessHandler.onLogoutSuccess(request, response, auth);

            return;
        }

        chain.doFilter(request, response);
    }

?作用:處理登出請求

  • doFilter前:如果是登出請求拳喻,則進行登出處理和登出成功后處理哭当。通過http.logout().addLogoutHandler().logoutSuccessHandler()可以增加處理機制
  • doFilter后:無
?6. UsernamePasswordAuthenticationFilter

?名稱:用戶認證過濾器
?繼承AbstractAuthenticationProcessingFilter,是基于瀏覽器的基于HTTP的身份驗證請求的抽象處理器
?源碼:

// AbstractAuthenticationProcessingFilter的doFilter方法
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;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Request is to process authentication");
        }

        Authentication authResult;

        try {
            // 嘗試去獲取認證對象
            authResult = attemptAuthentication(request, response);
            if (authResult == null) {
                // return immediately as subclass has indicated that it hasn't completed
                // authentication
                return;
            }
            // session處理機制處理認證對象冗澈,默認為NullAuthenticatedSessionStrategy不進行處理
            sessionStrategy.onAuthentication(authResult, request, response);
        }
        catch (InternalAuthenticationServiceException failed) {
            logger.error(
                    "An internal error occurred while trying to authenticate the user.",
                    failed);
            unsuccessfulAuthentication(request, response, failed);

            return;
        }
        catch (AuthenticationException failed) {
            // Authentication failed
            unsuccessfulAuthentication(request, response, failed);

            return;
        }

        // 認證成功后判斷是否繼續(xù)執(zhí)行其他過濾器
        if (continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
        //
        successfulAuthentication(request, response, chain, authResult);
    }
// UsernamePasswordAuthenticationFilter的attemptAuthentication方法
public Authentication attemptAuthentication(HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        // 從請求中獲取參數(shù)名為'username','password'的值
        String username = obtainUsername(request);
        String password = obtainPassword(request);

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();
        // 通過用戶名密碼生成一個未經(jīng)驗證的認證對象
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        // 通過認證管理器對該認證對象進行認證
        return this.getAuthenticationManager().authenticate(authRequest);
    }

?作用:判斷用戶是否為登錄請求钦勘,如果是登錄請求,提取其中的用戶名和密碼并保存認證對象到SecurityContextHolder中

  • doFilter前:如果不是登錄請求亚亲,跳過當前過濾彻采。如果是登錄請求,則嘗試去獲取認證對象捌归。認證對象主要是通過認證管理器下認證機制對包含用戶名和密碼的對象進行認證并返回肛响。
  • 如果continueChainBeforeSuccessfulAuthentication屬性為false,則不執(zhí)行后置過濾。如果為true惜索。執(zhí)行后置過濾
  • doFilter后:如果不執(zhí)行后置過濾或者執(zhí)行完后置過濾后去執(zhí)行一些成功登錄的處理特笋,包括保存認證對象、rememberMe記錄巾兆、成功后的自定義處理器處理
?7. DefaultLoginPageGeneratingFilter

?名稱:生成默認登錄頁過濾器
?作用:生成默認的登錄頁

  • doFilter前:生成登錄頁
  • doFilter后:無
?8. DefaultLogoutPageGeneratingFilter

?名稱:生成默認登出頁過濾器雹有,繼承OncePerRequestFilter
?作用:生成默認的登出頁

  • doFilter前:如果匹配默認/logout的GET請求偿渡,則渲染登出頁。如果不匹配霸奕,則跳過該過濾器溜宽。
  • doFilter后:無
?9. BasicAuthenticationFilter

?名稱:Http基本認證過濾器
?源碼:

    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain)
                    throws IOException, ServletException {
        final boolean debug = this.logger.isDebugEnabled();
        try {
            // 判斷是否包含請求頭Authorization,并且該值以BASIC開頭,將其值朱哪位認證對象
            UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request);
            if (authRequest == null) {
                chain.doFilter(request, response);
                return;
            }

            String username = authRequest.getName();

            if (debug) {
                this.logger
                        .debug("Basic Authentication Authorization header found for user '"
                                + username + "'");
            }
            // 需要對從請求頭中獲取的認證對象進行認證處理器進行處理
            if (authenticationIsRequired(username)) {
                // 認證管理器對該認證對象進行認證
                Authentication authResult = this.authenticationManager
                        .authenticate(authRequest);

                if (debug) {
                    this.logger.debug("Authentication success: " + authResult);
                }
                // 保存認證對象到SecurityContextHolder
                SecurityContextHolder.getContext().setAuthentication(authResult);
                // rememberMe記錄
                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);
    }

?作用:對Http請求進行基本認證,是對請求頭Authorization的認證

  • doFilter前:判斷是否包含請求頭Authorization,并且該值以BASIC開頭质帅。如果該值不可以轉(zhuǎn)換為認證對象适揉,跳過該過濾器。如果可以轉(zhuǎn)換為認證對象煤惩,如果需要對改對象進行認證處理嫉嘀,則使用認證管理器對其進行處理,然后依次保存認證對象魄揉、rememberMe記錄剪侮、成功后處理器處理
  • doFilter后:無
?10. RequestCacheAwareFilter

?名稱:請求緩存過濾器
?源碼:

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);
    }

?作用:緩存當前的請求,可以用作重新登錄后的再次請求使用

  • doFilter前:嘗試去獲取緩存的請求洛退,如果緩存請求不為null瓣俯,則doFilter中傳入緩存的請求,否則傳入原有的請求兵怯。
  • doFilter后:無
?11. SecurityContextHolderAwareRequestFilter

?名稱:SecurityContextHolder包裝請求過濾器
?源碼:

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

?作用:將request包裝彩匕,更適用于SecurityContext

  • doFilter前:將request和rolePrefix(角色前綴)、authenticationEntryPoint(認證異常處理)媒区、authenticationManager(認證管理器)驼仪、logoutHandlers(退出處理)、trustResolver(身份信任處理)綁定袜漩。
  • doFIlter后:無
?12. RememberMeAuthenticationFilter

?名稱:rememberMe認證過濾器
?源碼:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        // 本身是登錄的狀態(tài)绪爸,則不需要對rememberMe的對象進行認證
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            // 通過服務(wù)類從請求的rememerMe中拿到認證對象
            // rememberMeServices通常
            //  1.如果有PersistentTokenRepository token持久化庫,則會PersistentTokenBasedRememberMeServices服務(wù)宙攻,從持久庫中拿
            //  2.如果沒有持久化庫毡泻,則使用基本的rememberMe服務(wù),TokenBasedRememberMeServices
            Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
                    response);

            if (rememberMeAuth != null) {
                // 嘗試去通過認證管理器來認證rememberMe對象
                try {
                    rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);

                    // 保存至SecurityContextHolder中
                    SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);

                    onSuccessfulAuthentication(request, response, rememberMeAuth);

                    if (logger.isDebugEnabled()) {
                        logger.debug("SecurityContextHolder populated with remember-me token: '"
                                + SecurityContextHolder.getContext().getAuthentication()
                                + "'");
                    }

                    // Fire event
                    if (this.eventPublisher != null) {
                        eventPublisher
                                .publishEvent(new InteractiveAuthenticationSuccessEvent(
                                        SecurityContextHolder.getContext()
                                                .getAuthentication(), this.getClass()));
                    }

                    if (successHandler != null) {
                        // 認證成功后的處理
                        successHandler.onAuthenticationSuccess(request, response,
                                rememberMeAuth);

                        return;
                    }

                }
                catch (AuthenticationException authenticationException) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(
                                "SecurityContextHolder not populated with remember-me token, as "
                                        + "AuthenticationManager rejected Authentication returned by RememberMeServices: '"
                                        + rememberMeAuth
                                        + "'; invalidating remember-me token",
                                authenticationException);
                    }

                    rememberMeServices.loginFail(request, response);

                    onUnsuccessfulAuthentication(request, response,
                            authenticationException);
                }
            }

            chain.doFilter(request, response);
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
                        + SecurityContextHolder.getContext().getAuthentication() + "'");
            }

            chain.doFilter(request, response);
        }
    }

?作用:認證rememberMe的對象

  • doFilter前:如果非登錄狀態(tài)粘优,并且可以從請求中rememberMe相關(guān)中拿到認證對象仇味。那么就使用認證管理器進行認證,之后成功后進行成功認證處理雹顺。否則丹墨,則跳過該過濾器
  • doFilter后:無

?其他:關(guān)于remember的一些配置℃依ⅲ可以使用http.rememberMe()方法獲取配置對象贩挣,進行一些配置。
?例如:

  • tokenRepository()配置rememberMe對象獲取服務(wù)
  • rememberMeParameter()登錄時表示remememberMe的參數(shù)
  • rememberMeCookieName()配置rememberMe的cookie名稱
  • 可以參考RememberMeConfigurer類的源碼
?13. AnonymousAuthenticationFilter

?名稱:匿名認證過濾器
?源碼:

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);
    }

?作用:檢測SecurityContextHolder中是否有認證對象,如果沒有王财,就創(chuàng)建一個匿名訪問對象卵迂。

  • doFilter前:檢測SecurityContextHolder中是否有認證對象,如果沒有绒净,就創(chuàng)建一個匿名訪問對象
  • doFilter后:無
?14. SessionManagementFilter

?名稱:會話管理過濾器
?源碼:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        // 過濾器只執(zhí)行一次
        if (request.getAttribute(FILTER_APPLIED) != null) {
            chain.doFilter(request, response);
            return;
        }

        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

        // 如果SecurityContext持久庫中包含本次請求的context见咒。不包含則進行一系列處理
        if (!securityContextRepository.containsContext(request)) {
            // 本次請求的認證對象
            Authentication authentication = SecurityContextHolder.getContext()
                    .getAuthentication();

            // 認證對象不為null且不是匿名對象
            if (authentication != null && !trustResolver.isAnonymous(authentication)) {
                // The user has been authenticated during the current request, so call the
                // session strategy
                try {
                    // session處理
                    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.
                // 重新再保存至SecurityContext持久庫中
                securityContextRepository.saveContext(SecurityContextHolder.getContext(),
                        request, response);
            }
            else {
                // 如果認證對象不存在,則檢查session是否過期
                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);
    }

?作用:執(zhí)行與會話相關(guān)的一些設(shè)置

  • doFilter前:如果持久庫中存在本次請求的securityContext挂疆,跳過該過濾器改览。如果不存在,則需要做session處理缤言。
    • 匿名對象宝当,檢測session是否過期
    • 其他認證對象,通過SessionAuthenticationStrategy的實際對象做出相應(yīng)的處理胆萧,然后將處理后的認證對象重新保存在SecurityContext持久庫中
  • doFilter后:無
?15. ExceptionTranslationFilter

?名稱:異常事務(wù)過濾器
?源碼:

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) {
                if (response.isCommitted()) {
                    throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
                }
                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);
            }
        }
    }

?作用:對過濾器中拋出的異常進行處理

  • doFilter前:無
  • doFilter后:
    • 如果是AuthenticationException異常庆揩,需要AuthenticationEntryPoint對象處理
    • 如果是AccessDeniedException異常,需要AccessDeniedHandler對象處理
    • 如果是其他異常跌穗,繼續(xù)向上拋

?其他:可以通過http. exceptionHandling().authenticationEntryPoint().accessDeniedHandler()來進行添加订晌。

?16. FilterSecurityInterceptor

?名稱:安全攔截器
?源碼:

// FilterSecurityInterceptor過濾方法
public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        // 包裝request, response, chain
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }
public void invoke(FilterInvocation fi) throws IOException, ServletException {
        // request不為null AND 并且不是第一次過濾 AND observeOncePerRequest(每一個請求觀察一次,默認為true)
        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 {
            // 控制請求被檢查一次
            if (fi.getRequest() != null && observeOncePerRequest) {
                fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
            }

            // 過濾前的處理
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                // 再次保存SecurityContext至SecurityContextHodler中
                super.finallyInvocation(token);
            }

            // 后置處理,使用后置管理器(AfterInvocationManager)處理
            super.afterInvocation(token, null);
        }
    }
// AbstractSecurityInterceptor的beforeInvocation方法
protected InterceptorStatusToken beforeInvocation(Object object) {
        Assert.notNull(object, "Object was null");
        final boolean debug = logger.isDebugEnabled();

        if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
            throw new IllegalArgumentException(
                    "Security invocation attempted for object "
                            + object.getClass().getName()
                            + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
                            + getSecureObjectClass());
        }

        // 獲取驗證方式的spel表達式集合
        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
                .getAttributes(object);

        if (attributes == null || attributes.isEmpty()) {
            if (rejectPublicInvocations) {
                throw new IllegalArgumentException(
                        "Secure object invocation "
                                + object
                                + " was denied as public invocations are not allowed via this interceptor. "
                                + "This indicates a configuration error because the "
                                + "rejectPublicInvocations property is set to 'true'");
            }

            if (debug) {
                logger.debug("Public object - authentication not attempted");
            }

            publishEvent(new PublicInvocationEvent(object));

            return null; // no further work post-invocation
        }

        if (debug) {
            logger.debug("Secure object: " + object + "; Attributes: " + attributes);
        }

        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(messages.getMessage(
                    "AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"),
                    object, attributes);
        }

        // 如果認證對象仍然需要認證瞻离,則使用認證管理器進行認證
        Authentication authenticated = authenticateIfRequired();

        // Attempt authorization
        try {
            // 對權(quán)限進行驗證,即對access()腾仅、authenticated()乒裆、hasRole()套利、hasAuthority()進行驗證。
            // 驗證處理方式是使用驗證管理器進行驗證鹤耍。
            //  1.AffirmativeBased 非否決通過
            //  2.ConsensusBased 少數(shù)服從多數(shù)
            //  3.UnanimousBased 一致通過
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                    accessDeniedException));

            throw accessDeniedException;
        }

        if (debug) {
            logger.debug("Authorization successful");
        }

        if (publishAuthorizationSuccess) {
            publishEvent(new AuthorizedEvent(object, attributes, authenticated));
        }

        // Attempt to run as a different user
        Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
                attributes);

        if (runAs == null) {
            if (debug) {
                logger.debug("RunAsManager did not change Authentication object");
            }

            // no further work post-invocation
            return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
                    attributes, object);
        }
        else {
            if (debug) {
                logger.debug("Switching to RunAs Authentication: " + runAs);
            }

            SecurityContext origCtx = SecurityContextHolder.getContext();
            SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
            SecurityContextHolder.getContext().setAuthentication(runAs);

            // need to revert to token.Authenticated post-invocation
            return new InterceptorStatusToken(origCtx, true, attributes, object);
        }
    }
private Authentication authenticateIfRequired() {
        Authentication authentication = SecurityContextHolder.getContext()
                .getAuthentication();

        // 如果認證對象已經(jīng)得到認證 并且 !alwaysReauthenticate(不是一定需要認證)
        if (authentication.isAuthenticated() && !alwaysReauthenticate) {
            if (logger.isDebugEnabled()) {
                logger.debug("Previously Authenticated: " + authentication);
            }

            return authentication;
        }

        // 認證管理器認證
        authentication = authenticationManager.authenticate(authentication);

        // We don't authenticated.setAuthentication(true), because each provider should do
        // that
        if (logger.isDebugEnabled()) {
            logger.debug("Successfully Authenticated: " + authentication);
        }

        SecurityContextHolder.getContext().setAuthentication(authentication);

        return authentication;
    }

?作用:攔截之前過濾器過濾后的認證對象肉迫,然后對其進行最終的身份認證或者是權(quán)限驗證。

  • doFilter前:獲取匹配該請求的權(quán)限驗證spel表達式稿黄,如果任然需要認證喊衫,可以使用認證管理器再次進行認證。然后執(zhí)行權(quán)限驗證杆怕,驗證表達式是否通過族购。(通過有三種模式:一票通過,少數(shù)服從多數(shù)陵珍,全票通過)
  • doFilter后:使用后置管理器進行處理寝杖。
?17. MethodSecurityInterceptor

?名稱:方法攔截器
?源碼:

public Object invoke(MethodInvocation mi) throws Throwable {
    InterceptorStatusToken token = super.beforeInvocation(mi);

    Object result;
    try {
        result = mi.proceed();
    }
    finally {
        super.finallyInvocation(token);
    }
    return super.afterInvocation(token, result);
}

?作用:如果配置文件中開啟@EnableGlobalMethodSecurity,并且開了prePostEnabled或securedEnabled或jsr250Enabled等就會使用到該攔截器互纯,通過切面的方式瑟幕,在業(yè)務(wù)方法前后進行認證或驗證。

四、 認證管理器和驗證管理器
?1. 認證管理器—AuthenticationManager

?源碼:

public interface AuthenticationManager {

   /**
    * 主要是進行身份認證只盹,認證成功返回認證后的對象辣往,認證不成功則拋出相應(yīng)的異常AuthenticationException
    */
   Authentication authenticate(Authentication authentication)
         throws AuthenticationException;
}

?實現(xiàn)類:ProviderManager

?源碼:

public Authentication authenticate(Authentication authentication)
      throws AuthenticationException {
   Class<? extends Authentication> toTest = authentication.getClass();
   AuthenticationException lastException = null;
   AuthenticationException parentException = null;
   Authentication result = null;
   Authentication parentResult = null;
   boolean debug = logger.isDebugEnabled();

   // 獲取所有的認證處理
   for (AuthenticationProvider provider : getProviders()) {
      // 判斷當前的認證處理是否支持對當前的認證對象的認證
      if (!provider.supports(toTest)) {
         continue;
      }

      if (debug) {
         logger.debug("Authentication attempt using "
               + provider.getClass().getName());
      }

      try {
         // 執(zhí)行認證處理的認證方法
         result = provider.authenticate(authentication);

         // 認證后的結(jié)果不為null的話,則認為返回是通過認證的認證對象
         if (result != null) {
            // 如果認證后的對象沒有細節(jié)內(nèi)容殖卑,則將原有的認證對象的細節(jié)內(nèi)容填充至認證后的對象中
            copyDetails(authentication, result);
            break;
         }
      }
      catch (AccountStatusException | InternalAuthenticationServiceException e) {
         prepareException(e, authentication);
         // SEC-546: Avoid polling additional providers if auth failure is due to
         // invalid account status
         throw e;
      } catch (AuthenticationException e) {
         lastException = e;
      }
   }

   // 經(jīng)過當前提供的一系列認證處理后仍然返回null 并且 該認證管理器具有父級處理認證的方式(需要新建實例時傳入)
   if (result == null && parent != null) {
      // Allow the parent to try.
      try {
         // 那么就可以使用父級認證管理器來對當前的認證對象進行認證
         result = parentResult = parent.authenticate(authentication);
      }
      catch (ProviderNotFoundException e) {
         // ignore as we will throw below if no other exception occurred prior to
         // calling parent and the parent
         // may throw ProviderNotFound even though a provider in the child already
         // handled the request
      }
      catch (AuthenticationException e) {
         lastException = parentException = e;
      }
   }

   if (result != null) {
      if (eraseCredentialsAfterAuthentication
            && (result instanceof CredentialsContainer)) {
         // Authentication is complete. Remove credentials and other secret data
         // from authentication
         ((CredentialsContainer) result).eraseCredentials();
      }

      // If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
      // This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
      if (parentResult == null) {
         eventPublisher.publishAuthenticationSuccess(result);
      }
      return result;
   }

   // Parent was null, or didn't authenticate (or throw an exception).

   if (lastException == null) {
      lastException = new ProviderNotFoundException(messages.getMessage(
            "ProviderManager.providerNotFound",
            new Object[] { toTest.getName() },
            "No AuthenticationProvider found for {0}"));
   }

   // If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
   // This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
   if (parentException == null) {
      prepareException(lastException, authentication);
   }

   throw lastException;
}

?作用:通過配置多套認證處理(AuthenticationProvider)站削,以應(yīng)對不同情況下的認證情形,是對認證的擴展懦鼠。

?認證處理類:AuthenticationProvider

?源碼:

public interface AuthenticationProvider {

   /**
    * 對認證對象進行認證
    * 返回認證后的對象表示已經(jīng)被認證
    * 返回null表示不做處理
    * 拋出異常钻哩,表示認證失敗
    */
   Authentication authenticate(Authentication authentication)
         throws AuthenticationException;

   /**
    * 支持的認證對象實體類
    * 返回true表示支持
    * 返回false表示不支持,將不進行authenticate認證
    */
   boolean supports(Class<?> authentication);
}

?作用:認證處理肛冶,對每一個支持的認證對象的身份進行認證街氢。

?關(guān)于AuthenticationManager的配置
  • 使用http. authenticationProvider()方法直接添加認證處理。
public void configure(HttpSecurity http) throws Exception {
    http
            .authenticationProvider(new AuthenticationProvider(){
                @Override
                public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                    System.out.println("認證000");
                    return authentication;
                }

                @Override
                public boolean supports(Class<?> authentication) {
                    return true;
                }
            })
            .authorizeRequests()
            .antMatchers("/test").access("hasAnyRole('ADMIN', 'USER')")
            .anyRequest().authenticated()
            .and()
            // .formLogin().and()
            .rememberMe().and() // rememberMe過濾器 -- RememberMeAuthenticationFilter
            .httpBasic();
}

?說明:該AuthenticationProvider是直接插入到默認的認證管理器中睦袖。與下列的幾個配置方法不沖突

  • 繼承WebSecurityConfigurerAdapter類時覆寫authenticationManager()方法
class CustomizeWebSecurityConfigurer extends WebSecurityConfigurerAdapter{
    @Override
    public void configure(HttpSecurity http) throws Exception {
       
        http
                .authorizeRequests()
                .antMatchers("/test").access("hasAnyRole('ADMIN', 'USER')")
                .anyRequest().authenticated()
                .and()
                // .formLogin().and()
                .rememberMe().and() // rememberMe過濾器 -- RememberMeAuthenticationFilter
                .httpBasic();
    }

    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        AuthenticationProvider provider = new AuthenticationProvider(){
            @Override
            public Authentication authenticate(Authentication authentication) throws     AuthenticationException {
                System.out.println("認證111");
                return null;
            }

            @Override
            public boolean supports(Class<?> authentication) {
                return true;
            }
        };
        ProviderManager providerManager = new ProviderManager(Arrays.asList(provider));
        return providerManager;
    }

}

?說明:該AuthenticationManager是被注冊到FilterSecurityInterceptor的默認AuthenticationManager的parent認證管理器中

  • 直接向Spring中注冊AuthenticationManager的Bean
@Bean
public AuthenticationManager authenticationManager() {
    AuthenticationProvider provider = new AuthenticationProvider(){
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            System.out.println("認證333");
            return authentication;
        }

        @Override
        public boolean supports(Class<?> authentication) {
            return true;
        }
    };
    ProviderManager providerManager = new ProviderManager(Arrays.asList(provider));
    return providerManager;
}

?說明:該AuthenticationManager是被注冊到FilterSecurityInterceptor的默認AuthenticationManager的parent認證管理器中珊肃,也會注冊到MethodSecurityInterceptor認證管理器中。

  • 可以向Spring注冊AuthenticationProvider
@Bean
public AuthenticationProvider authenticationProvider() {
    return new AuthenticationProvider(){
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            System.out.println("認證222");
            return authentication;
        }

        @Override
        public boolean supports(Class<?> authentication) {
            return true;
        }
    };
}

?說明:注入AuthenticationProvider只能提供一個對象的注冊馅笙,并且該AuthenticationProvider是注冊到FilterSecurityInterceptor的默認AuthenticationManager的parent認證管理器中伦乔,也會注冊到MethodSecurityInterceptor認證管理器中。

?FilterSecurityInterceptor優(yōu)先等級如下:
  • http. authenticationProvider最優(yōu)先使用董习。
  • 繼承覆寫 > AuthenticationProvider > AuthenticationManager
    ?源碼:
@Bean
public AuthenticationProvider authenticationProvider() {
    return new AuthenticationProvider(){
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            System.out.println("認證222");
            return null;
        }

        @Override
        public boolean supports(Class<?> authentication) {
            return true;
        }
    };
}

/**
 * 傳入自定義的認證管理器
 * @return
 */
@Bean
public AuthenticationManager authenticationManager() {
    AuthenticationProvider provider = new AuthenticationProvider(){
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            System.out.println("認證333");
            return null;
        }

        @Override
        public boolean supports(Class<?> authentication) {
            return true;
        }
    };
    ProviderManager providerManager = new ProviderManager(Arrays.asList(provider));
    return providerManager;
}

@Order(1)
class CustomizeWebSecurityConfigurer extends WebSecurityConfigurerAdapter{
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authenticationProvider(new AuthenticationProvider(){
                    @Override
                    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                        System.out.println("認證000");
                        return null;
                    }

                    @Override
                    public boolean supports(Class<?> authentication) {
                        return true;
                    }
                })
                .authorizeRequests()
                .antMatchers("/test").access("hasAnyRole('ADMIN', 'USER')")
                .anyRequest().authenticated()
                .and()
                // .formLogin().and()
                .rememberMe().and() // rememberMe過濾器 -- RememberMeAuthenticationFilter
                .httpBasic();
    }

    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        AuthenticationProvider provider = new AuthenticationProvider(){
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                System.out.println("認證111");
                return null;
            }

            @Override
            public boolean supports(Class<?> authentication) {
                return true;
            }
        };
        ProviderManager providerManager = new ProviderManager(Arrays.asList(provider));
        return providerManager;
    }

}

?說明:當前情況下先執(zhí)行”認證000”烈和,由于返回null,所以執(zhí)行”認證111”皿淋,如果去掉覆寫招刹,執(zhí)行”認證222”,如果去掉AuthenticationProvider注冊窝趣,執(zhí)行”認證333”

?2. 驗證管理器—AccessDecisionManager

?關(guān)于驗證管理器的配置:

  • 使用http. authorizeRequests().accessDecisionManager()方法配置
class OtherWebSecurityConfigurer extends WebSecurityConfigurerAdapter{
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests().accessDecisionManager(new AffirmativeBased(Arrays.asList(new WebExpressionVoter())))
                .anyRequest().authenticated()
                .and()
                .formLogin().and()
                .httpBasic();
    }
}

五疯暑、 全局方法的注解權(quán)限驗證和過濾

?1. @EnableGlobalMethodSecurity注解

?開啟全局的驗證方法,下面解釋一下常用的注解

?2. prePostEnabled

?說明:設(shè)置true時哑舒,將可以使用@ PreAuthorize妇拯、@PostAuthorize、@PreFilter洗鸵、@PostFilter注解

?參考:開啟prePostEnabled

  • PreAuthorize:前置權(quán)限驗證越锈。

  • PostAuthorize:后置權(quán)限驗證。

  • PreFilter:前置過濾膘滨。

  • PostFilter:后置過濾

?3. securedEnabled

?說明:設(shè)置true時甘凭,允許使用SpringSecurity內(nèi)置的方法驗證±艋觯可以使用@Secured

?參考:開啟Secured

?4. jsr250Enabled

?說明:設(shè)置true時对蒲,允許使用JSR250注解進行驗證钩蚊。

六、 總結(jié)

?1. 通常情況下

?在FilterSecurityInterceptor過濾器之前蹈矮,SecurityContextHolder中存在認證對象砰逻,即算通過認證。

?2. 權(quán)限驗證

?authenticated()驗證通過的是非匿名對象泛鸟,并不是使用Authentication認證對象的isAuthenticated()方法進行驗證蝠咆。

?3. 注解@EnableGlobalMethodSecurity

?使用該注解后,會使用代理模式北滥。通過MethodSecurityInterceptorinvoke()方法來對業(yè)務(wù)方法進行驗證等操作

?4. 過濾器參考圖
SpringSecurity過濾流程
?5. 其他

?SpringSecurity參考文檔:參考文檔
?SpringSecurity中文參考文檔:中文參考文檔
?核心源碼及注釋下載:網(wǎng)盤鏈接 提取碼: bwpv
?關(guān)于Spring源碼調(diào)試及注釋:文章鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刚操,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子再芋,更是在濱河造成了極大的恐慌菊霜,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件济赎,死亡現(xiàn)場離奇詭異鉴逞,居然都是意外死亡,警方通過查閱死者的電腦和手機司训,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門构捡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人壳猜,你說我怎么就攤上這事勾徽。” “怎么了统扳?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵喘帚,是天一觀的道長。 經(jīng)常有香客問我闪幽,道長啥辨,這世上最難降的妖魔是什么涡匀? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任盯腌,我火速辦了婚禮,結(jié)果婚禮上陨瘩,老公的妹妹穿的比我還像新娘腕够。我一直安慰自己,他們只是感情好舌劳,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布帚湘。 她就那樣靜靜地躺著,像睡著了一般甚淡。 火紅的嫁衣襯著肌膚如雪大诸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音资柔,去河邊找鬼焙贷。 笑死,一個胖子當著我的面吹牛贿堰,可吹牛的內(nèi)容都是我干的辙芍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼羹与,長吁一口氣:“原來是場噩夢啊……” “哼故硅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纵搁,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吃衅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后腾誉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捐晶,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年妄辩,在試婚紗的時候發(fā)現(xiàn)自己被綠了惑灵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡眼耀,死狀恐怖英支,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哮伟,我是刑警寧澤干花,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站楞黄,受9級特大地震影響池凄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鬼廓,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一肿仑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碎税,春花似錦尤慰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至匪煌,卻和暖如春责蝠,著一層夾襖步出監(jiān)牢的瞬間党巾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工霜医, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留昧港,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓支子,卻偏偏與公主長得像创肥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子值朋,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容