神奇!本身 new 出來的對象同樣也能夠被 Spring 容器管理!

按理說本身 new 出來的對象和容器是沒有關係的,可是在 Spring Security 框架中也 new 了不少對象出來,同樣也能夠被容器管理,那麼它是怎麼作到的?java

今天來和你們聊一個略微冷門的話題,Spring Security 中的 ObjectPostProcessor 究竟是幹嗎用的?web

本文是 Spring Security 系列第 32 篇,閱讀前面文章有助於更好的理解本文:spring

  1. 挖一個大坑,Spring Security 開搞!
  2. 鬆哥手把手帶你入門 Spring Security,別再問密碼怎麼解密了
  3. 手把手教你定製 Spring Security 中的表單登陸
  4. Spring Security 作先後端分離,咱就別作頁面跳轉了!通通 JSON 交互
  5. Spring Security 中的受權操做原來這麼簡單
  6. Spring Security 如何將用戶數據存入數據庫?
  7. Spring Security+Spring Data Jpa 強強聯手,安全管理只有更簡單!
  8. Spring Boot + Spring Security 實現自動登陸功能
  9. Spring Boot 自動登陸,安全風險要怎麼控制?
  10. 在微服務項目中,Spring Security 比 Shiro 強在哪?
  11. SpringSecurity 自定義認證邏輯的兩種方式(高級玩法)
  12. Spring Security 中如何快速查看登陸用戶 IP 地址等信息?
  13. Spring Security 自動踢掉前一個登陸用戶,一個配置搞定!
  14. Spring Boot + Vue 先後端分離項目,如何踢掉已登陸用戶?
  15. Spring Security 自帶防火牆!你都不知道本身的系統有多安全!
  16. 什麼是會話固定攻擊?Spring Boot 中要如何防護會話固定攻擊?
  17. 集羣化部署,Spring Security 要如何處理 session 共享?
  18. 鬆哥手把手教你在 SpringBoot 中防護 CSRF 攻擊!so easy!
  19. 要學就學透徹!Spring Security 中 CSRF 防護源碼解析
  20. Spring Boot 中密碼加密的兩種姿式!
  21. Spring Security 要怎麼學?爲何必定要成體系的學習?
  22. Spring Security 兩種資源放行策略,千萬別用錯了!
  23. 鬆哥手把手教你入門 Spring Boot + CAS 單點登陸
  24. Spring Boot 實現單點登陸的第三種方案!
  25. Spring Boot+CAS 單點登陸,如何對接數據庫?
  26. Spring Boot+CAS 默認登陸頁面太醜了,怎麼辦?
  27. 用 Swagger 測試接口,怎麼在請求頭中攜帶 Token?
  28. Spring Boot 中三種跨域場景總結
  29. Spring Boot 中如何實現 HTTP 認證?
  30. Spring Security 中的四種權限控制方式
  31. Spring Security 多種加密方案共存,老破舊系統整合利器!

若是你們研究過鬆哥的微人事項目,就會發現裏邊的動態權限配置有這樣一行代碼:數據庫

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(customUrlDecisionManager);
                        object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                        return object;
                    }
                })
                .and()
                ...
    }
}

這裏的 withObjectPostProcessor 到底該如何理解?後端

今天鬆哥就來和你們揭開謎題。跨域

1.ObjectPostProcessor 的做用

咱們先來看下 ObjectPostProcessor 到底有啥做用,先來看一下這個接口的定義:安全

package org.springframework.security.config.annotation;
public interface ObjectPostProcessor<T> {
    <O extends T> O postProcess(O object);
}

接口中就只有一個 postProcess 方法。session

咱們再來看看 ObjectPostProcessor 的繼承關係:框架

兩個比較重要的實現類,其中 AutowireBeanFactoryObjectPostProcessor 值得一說,來看下 AutowireBeanFactoryObjectPostProcessor 的定義:前後端分離

final class AutowireBeanFactoryObjectPostProcessor
        implements ObjectPostProcessor<Object>, DisposableBean, SmartInitializingSingleton {
    AutowireBeanFactoryObjectPostProcessor(
            AutowireCapableBeanFactory autowireBeanFactory) {
        this.autowireBeanFactory = autowireBeanFactory;
    }
    @SuppressWarnings("unchecked")
    public <T> T postProcess(T object) {
        if (object == null) {
            return null;
        }
        T result = null;
        try {
            result = (T) this.autowireBeanFactory.initializeBean(object,
                    object.toString());
        }
        catch (RuntimeException e) {
            Class<?> type = object.getClass();
            throw new RuntimeException(
                    "Could not postProcess " + object + " of type " + type, e);
        }
        this.autowireBeanFactory.autowireBean(object);
        if (result instanceof DisposableBean) {
            this.disposableBeans.add((DisposableBean) result);
        }
        if (result instanceof SmartInitializingSingleton) {
            this.smartSingletons.add((SmartInitializingSingleton) result);
        }
        return result;
    }
}

AutowireBeanFactoryObjectPostProcessor 的源碼很好理解:

  1. 首先在構建 AutowireBeanFactoryObjectPostProcessor 對象時,傳入了一個 AutowireCapableBeanFactory 對象,看過 Spring 源碼的小夥伴就知道,AutowireCapableBeanFactory 能夠幫助咱們手動的將一個實例註冊到 Spring 容器中。
  2. 在 postProcess 方法中,就是具體的註冊邏輯了,都很簡單,我就再也不贅述。

因而可知,ObjectPostProcessor 的主要做用就是手動註冊實例到 Spring 容器中去(而且讓實例走一遍 Bean 的生命週期)。

正常來講,咱們項目中的 Bean 都是經過自動掃描注入到 Spring 容器中去的,然而在 Spring Security 框架中,有大量的代碼不是經過自動掃描注入到 Spring 容器中去的,而是直接 new 出來的,這樣作的本意是爲了簡化項目配置。

這些直接 new 出來的代碼,若是想被 Spring 容器管理該怎麼辦呢?那就得 ObjectPostProcessor 出場了。

2.框架舉例

接下來我隨便舉幾個框架中對象 new 的例子,你們看一下 ObjectPostProcessor 的做用:

HttpSecurity 初始化代碼(WebSecurityConfigurerAdapter#getHttp):

protected final HttpSecurity getHttp() throws Exception {
    if (http != null) {
        return http;
    }
    ...
    ...
    http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
            sharedObjects);
    ...
    ...
}

WebSecurity 初始化代碼(WebSecurityConfiguration#setFilterChainProxySecurityConfigurer):

public void setFilterChainProxySecurityConfigurer(
        ObjectPostProcessor<Object> objectPostProcessor,
        @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
        throws Exception {
    webSecurity = objectPostProcessor
            .postProcess(new WebSecurity(objectPostProcessor));
    ...
    ...
}

Spring Security 框架源碼中,隨處可見手動裝配。Spring Security 中,過濾器鏈中的全部過濾器都是經過對應的 xxxConfigure 來進行配置的,而全部的 xxxConfigure 都是繼承自 SecurityConfigurerAdapter,以下:

而在這些 xxxConfigure 的 configure 方法中,無一例外的都會讓他們各自配置的管理器去 Spring 容器中走一圈,例如 AbstractAuthenticationFilterConfigurer#configure 方法:

public void configure(B http) throws Exception {
    ...
    ...
    F filter = postProcess(authFilter);
    http.addFilter(filter);
}

其餘的 xxxConfigurer#configure 方法也都有相似的實現,小夥伴們能夠自行查看,我就再也不贅述了。

3.爲何這樣

直接將 Bean 經過自動掃描註冊到 Spring 容器很差嗎?爲何要搞成這個樣子?

在 Spring Security 中,因爲框架自己大量採用了 Java 配置,而且沒有將對象的各個屬性都暴露出來,這樣作的本意是爲了簡化配置。然而這樣帶來的一個問題就是須要咱們手動將 Bean 註冊到 Spring 容器中去,ObjectPostProcessor 就是爲了解決該問題。

一旦將 Bean 註冊到 Spring 容器中了,咱們就有辦法去加強一個 Bean 的功能,或者需修改一個 Bean 的屬性。

例如一開始提到的動態權限配置代碼:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(customUrlDecisionManager);
                        object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                        return object;
                    }
                })
                .and()
                ...
    }
}

權限管理自己是由 FilterSecurityInterceptor 控制的,系統默認的 FilterSecurityInterceptor 已經建立好了,並且我也沒辦法修改它的屬性,那麼怎麼辦呢?咱們能夠利用 withObjectPostProcessor 方法,去修改 FilterSecurityInterceptor 中的相關屬性。

上面這個配置生效的緣由之一是由於 FilterSecurityInterceptor 在建立成功後,會重走一遍 postProcess 方法,這裏經過重寫 postProcess 方法就能實現屬性修改,咱們能夠看下配置 FilterSecurityInterceptor 的方法(AbstractInterceptUrlConfigurer#configure):

abstract class AbstractInterceptUrlConfigurer<C extends AbstractInterceptUrlConfigurer<C, H>, H extends HttpSecurityBuilder<H>>
        extends AbstractHttpConfigurer<C, H> {
    @Override
    public void configure(H http) throws Exception {
        FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
        if (metadataSource == null) {
            return;
        }
        FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(
                http, metadataSource, http.getSharedObject(AuthenticationManager.class));
        if (filterSecurityInterceptorOncePerRequest != null) {
            securityInterceptor
                    .setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);
        }
        securityInterceptor = postProcess(securityInterceptor);
        http.addFilter(securityInterceptor);
        http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
    }
}

能夠看到,securityInterceptor 對象建立成功後,仍是會去 postProcess 方法中走一遭。

看懂了上面的代碼,接下來我再舉一個例子小夥伴們應該一下就能明白:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                ...
                .and()
                .formLogin()
                .withObjectPostProcessor(new ObjectPostProcessor<UsernamePasswordAuthenticationFilter>() {
                    @Override
                    public <O extends UsernamePasswordAuthenticationFilter> O postProcess(O object) {
                        object.setUsernameParameter("name");
                        return object;
                    }
                })
                ...
    }
}

在這裏,我把配置好的 UsernamePasswordAuthenticationFilter 過濾器再拎出來,修改一下用戶名的 key(正常來講,修改用戶名的 key 不用這麼麻煩,這裏主要是給你們演示 ObjectPostProcessor 的效果),修改完成後,之後用戶登陸時,用戶名就不是 username 而是 name 了。

4.小結

好了,只要小夥伴們掌握了上面的用法,之後在 Spring Security 中,若是想修改某一個對象的屬性,可是殊不知道從哪裏下手,那麼不妨試試 withObjectPostProcessor!

小夥伴們若是以爲有收穫,記得點個在看鼓勵下鬆哥哦~

相關文章
相關標籤/搜索