Spring Security源碼分析

參考鏈接(主要參考此係列文章,截圖和補充總結等由debug程序、閱讀源碼得出)

https://juejin.im/post/5d8d66aee51d45783f5aa49e

 

一、三句話解釋框架原理

1、整個框架的核心是一個過濾器,這個過濾器名字叫springSecurityFilterChain類型是FilterChainProxy

2、核心過濾器裏面是過濾器鏈(列表),過濾器鏈的每個元素都是一組URL對應一組過濾器

3、WebSecurity用來創建FilterChainProxy過濾器,

HttpSecurity用來創建過濾器鏈的每個元素。

補充解釋:WebSecurity和HttpSecurity都是建造者

WebSecurity構建目標是FilterChainProxy對象

HttpSecurity的構建目標僅僅是FilterChainProxy中的一個SecurityFilterChain

@EnableWebSecurity註解,導入了WebSecurityConfiguration類

WebSecurityConfiguration中創建了建造者對象WebSecurity,和核心過濾器FilterChainProxy

 

二、框架接口設計

1、關注兩個東西:建造者和適配器,框架的用法就是通過適配器對建造者進行配置

2、框架用法是寫一個自定義配置類,繼承WebSecurityConfigurerAdapter,重寫幾個configure()方法

WebSecurityConfigurerAdapter就是Web安全配置器的適配器對象。

 

// 安全建造者 // 顧名思義是一個builder構造器,創建並返回一個類型爲O的對象

// 安全建造者
// 顧名思義是一個builder構造器,創建並返回一個類型爲O的對象
public interface SecurityBuilder<O> {
    O build() throws Exception;
}

// 抽象安全建造者
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
    private AtomicBoolean building = new AtomicBoolean();
    private O object;

    public final O build() throws Exception {
        // 限定build()只會進行一次!
        if (this.building.compareAndSet(false, true)) {
            this.object = doBuild();
            return this.object;
        }
        throw new AlreadyBuiltException("This object has already been built");
    }

    // 子類需要重寫doBuild()方法
    protected abstract O doBuild() throws Exception;
}

// 配置後的抽象安全建造者
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
        extends AbstractSecurityBuilder<O> {

    // 實現了doBuild()方法,遍歷configurers進行init()和configure()。
    protected final O doBuild() throws Exception {
        synchronized (configurers) {
            buildState = BuildState.INITIALIZING;

            beforeInit();
            init();

            buildState = BuildState.CONFIGURING;

            beforeConfigure();
            configure();

buildState = BuildState.BUILDING;

            O result = performBuild();

            buildState = BuildState.BUILT;

            return result;
        }
    }
    // 它的子類HttpSecurity和WebSecurity都實現了它的performBuild()方法!!!
    protected abstract O performBuild() throws Exception;

    // 主要作用是將安全配置器SecurityConfigurer注入到屬性configurers中,
    private void configure() throws Exception {
        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

        for (SecurityConfigurer<O, B> configurer : configurers) {
            configurer.configure((B) this);
        }
    }
}
// 安全配置器,配置建造者B,B可以建造O
// 初始化(init)SecurityBuilder,且配置(configure)SecurityBuilder
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
    void init(B builder) throws Exception;
    void configure(B builder) throws Exception;
}


// Web安全配置器,配置建造者T,T可以建造web過濾器
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> 
        extends SecurityConfigurer<Filter, T> {
}

// Web安全配置器的適配器
// 配置建造者WebSecurity,WebSecurity可以建造核心過濾器
public abstract class WebSecurityConfigurerAdapter 
        implements WebSecurityConfigurer<WebSecurity> {
}


// 用於構建FilterChainProxy的建造者
public final class WebSecurity 
    extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
    implements
        SecurityBuilder<Filter>, ApplicationContextAware {
}

// 用於構建SecurityFilterChain的建造者
public final class HttpSecurity 
    extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
    implements 
        SecurityBuilder<DefaultSecurityFilterChain>,
        HttpSecurityBuilder<HttpSecurity> {

}

3、總結:

看到建造者去看他的方法:build(); doBuid(); init(); configure(); performBuilde();

看到配置器去看他的方法: init(); config();

 

三、從寫MySecurityConfig時使用的@EnableWebSecurity註解開始看源碼:

1、@EnableWebSecurity註解導入了三個類,重點關注WebSecurityConfiguration

2、WebSecurityConfiguration中需要關注兩個方法:

setFilterChainProxySecurityConfigurer()方法:創建了WebSecurity建造者對象,用於後面建造FilterChainProxy過濾器。

springSecurityFilterChain()方法:調用WebSecurity.build(),建造出FilterChainProxy過濾器對象。

 

四、WebSecurity的創建過程(由WebSecurityConfiguration創建)

1、setFilterChainProxySecurityConfigurer()方法負責收集配置累對象列表webSecurityConfigurers,並創建WebSecurity:

/**
 * Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>}
 * instances used to create the web configuration.
 *
 * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a
 * {@link WebSecurity} instance
 * @param webSecurityConfigurers the
 * {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to
 * create the web configuration
 * @throws Exception
 */
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
      ObjectPostProcessor<Object> objectPostProcessor,
      @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
      throws Exception {
   webSecurity = objectPostProcessor
         .postProcess(new WebSecurity(objectPostProcessor));
   if (debugEnabled != null) {
      webSecurity.debug(debugEnabled);
   }

   webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);

   Integer previousOrder = null;
   Object previousConfig = null;
   for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
      Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
      if (previousOrder != null && previousOrder.equals(order)) {
         throw new IllegalStateException(
               "@Order on WebSecurityConfigurers must be unique. Order of "
                     + order + " was already used on " + previousConfig + ", so it cannot be used on "
                     + config + " too.");
      }
      previousOrder = order;
      previousConfig = config;
   }
   for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
      webSecurity.apply(webSecurityConfigurer);
   }
   this.webSecurityConfigurers = webSecurityConfigurers;
}

2、下圖是通過AutowiredWebSecurityConfigurersIgnoreParents的getWebSecurityConfigurers()方法,獲取所有實現WebSecurityConfigurer的配置類。

 

 

五、FilterChainProxy的創建過程(由WebSecurityConfiguration創建)

1、在springSecurityFilterChain()方法中調用webSecurity.build()創建了FilterChainProxy。

PS:根據下面代碼,我們可以知道如果創建的MySecurityConfig類沒有被sping掃描到,

框架會新new 出一個WebSecurityConfigureAdapter對象,這會導致我們配置的用戶名和密碼失效。

 

 

2、WebSecurity是一個建造者,所以我們去看這些方法build(); doBuild(); init(); configure(); performBuild();

 

build()方法定義在WebSecurity對象的父類AbstractSecurityBuilder中:

 

build()方法會調用WebSecurity對象的父類AbstractConfiguredSecurityBuilder#doBuild():

 

doBuild()先調用init();configure();等方法

我們上面已經得知了configurersAddedInInitializing裏是所有的配置類對象

如下圖,這裏會依次執行配置類的configure();init()方法

 

doBuild()最後調用了WebSecurity對象的perfomBuild(),來創建了FilterChainProxy對象

performBuild()裏遍歷securityFilterChainBuilders建造者列表

把每個SecurityBuilder建造者對象構建成SecurityFilterChain實例

最後創建並返回FilterChainProxy

 

3、securityFilterChainBuilders建造者列表的初始化

 

這時候要注意到WebSecurityConfigurerAdapter,這個類的創建了HttpSecurity並放入了securityFilterChainBuilders

 

WebSecurityConfigurerAdapter是一個安全配置器,我們知道建造者在performBuild()之前都會把循環調用安全配置器的init();configure();方法,然後創建HttpSecurity並放入自己的securityFilterChainBuilders裏。

PS: 前面已經提到了,在WebSecurity初始化時,會依次將WebSecurityConfigurerAdapter的子類放入WebSecurity。

 

六、ServletContext如何拿到FilterChainProxy的過濾器對象

1、Bean都是存在Spring的Bean工廠裏的,而且在Web項目中Servlet、Filter、Listener都要放入ServletContext中。

 

2、看下面這張圖,ServletContainerInitializer接口提供了一個onStartup()方法,用於在Servlet容器啓動時動態註冊一些對象到ServletContext中。

官方的解釋是:爲了支持可以不使用web.xml。提供了ServletContainerInitializer,它可以通過SPI機制,當啓動web容器的時候,會自動到添加的相應jar包下找到META-INF/services下以ServletContainerInitializer的全路徑名稱命名的文件,它的內容爲ServletContainerInitializer實現類的全路徑,將它們實例化。

Spring框架通過META-INF配置了SpringServletContainerInitializer類

 

 

SpringServletContainerInitializer實現了ServletContainerInitializer接口。

請注意該類上的@HandlesTypes(WebApplicationInitializer.class)註解

根據Sevlet3.0規範,Servlet容器在調用onStartup()方法時,會以Set集合的方式注入WebApplicationInitializer的子類(包括接口,抽象類)。然後會依次調用WebApplicationInitializer的實現類的onStartup方法,從而起到啓動web.xml相同的作用(添加servlet,listener實例到ServletContext中)。

 

Spring Security中的AbstractSecurityWebApplicationInitializer就是WebApplicationInitializer的抽象子類.

當執行到下面的onStartup()方法時,會調用insertSpringSecurityFilterChain()

將類型爲FilterChainProxy名稱爲springSecurityFilterChain的過濾器對象用DelegatingFilterProxy包裝,然後注入ServletContext

 

七、FilterChainProxy運行過程

請求到達的時候,FilterChainProxy的dofilter()方法內部,會遍歷所有的SecurityFilterChain,對匹配到的url,則一一調用SecurityFilterChain中的filter做認證或授權。

public class FilterChainProxy extends GenericFilterBean {

    private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
    private List<SecurityFilterChain> filterChains;
    private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
    private HttpFirewall firewall = new StrictHttpFirewall();

    public FilterChainProxy() {
    }

    public FilterChainProxy(SecurityFilterChain chain) {
        this(Arrays.asList(chain));
    }

    public FilterChainProxy(List<SecurityFilterChain> filterChains) {
        this.filterChains = filterChains;
    }

    @Override
    public void afterPropertiesSet() {
        filterChainValidator.validate(this);
    }

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

        VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
        // 請求依次經過這組濾器鏈
        vfc.doFilter(fwRequest, fwResponse);
    }

    /**
     * 根據Request請求獲得一個過濾器鏈
     */
    private List<Filter> getFilters(HttpServletRequest request) {
        for (SecurityFilterChain chain : filterChains) {
            if (chain.matches(request)) {
                return chain.getFilters();
            }
        }
        return null;
    }

    /**
     * 根據URL獲得一個過濾器鏈
     */
    public List<Filter> getFilters(String url) {
        return getFilters(firewall.getFirewalledRequest((new FilterInvocation(url, null)
                .getRequest())));
    }

    /**
     * 返回一個過濾器鏈
     */
    public List<SecurityFilterChain> getFilterChains() {
        return Collections.unmodifiableList(filterChains);
    }

// 過濾器鏈內部類
    private static class VirtualFilterChain implements FilterChain {
        private final FilterChain originalChain;
        private final List<Filter> additionalFilters;
        private final FirewalledRequest firewalledRequest;
        private final int size;
        private int currentPosition = 0;

        private VirtualFilterChain(FirewalledRequest firewalledRequest,
                FilterChain chain, List<Filter> additionalFilters) {
            this.originalChain = chain;
            this.additionalFilters = additionalFilters;
            this.size = additionalFilters.size();
            this.firewalledRequest = firewalledRequest;
        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response)
                throws IOException, ServletException {
            if (currentPosition == size) {
                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                            + " reached end of additional filter chain; proceeding with original chain");
                }

                // Deactivate path stripping as we exit the security filter chain
                this.firewalledRequest.reset();

                originalChain.doFilter(request, response);
            }
            else {
                currentPosition++;

                Filter nextFilter = additionalFilters.get(currentPosition - 1);

                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                            + " at position " + currentPosition + " of " + size
                            + " in additional filter chain; firing Filter: '"
                            + nextFilter.getClass().getSimpleName() + "'");
                }

                nextFilter.doFilter(request, response, this);
            }
        }
    }

public interface FilterChainValidator {
        void validate(FilterChainProxy filterChainProxy);
    }

    private static class NullFilterChainValidator implements FilterChainValidator {
        @Override
        public void validate(FilterChainProxy filterChainProxy) {
        }
    }

}

補充:WebSecurity在初始化的時候會掃描WebSecurityConfigurerAdapter配置器適配器的子類(即生成HttpSecurity配置器)。

所有的配置器會被調用init();configure();初始化配置,其中生成的每個HttpSecurity配置器都代表了一個過濾器鏈。

 

PS:如果有多個WebSecurityConfigurerAdapter配置器適配器的子類,會產生多個SecurityFilterChain過濾器鏈實例。Spring Security Oauth2的拓展就是這麼做的。

 

八、spring security 怎麼創建的過濾器

1、我們已經知道了springSecurityFilterChain(類型爲FilterChainProxy)是實際起作用的過濾器鏈,DelegatingFilterProxy起到代理作用。

2、我們創建的MySecurityConfig繼承了WebSecurityConfigurerAdapter。WebSecurityConfigurerAdapter就是用來創建過濾器鏈,重寫的configure(HttpSecurity http)的方法就是用來配置HttpSecurity的。

protected void configure(HttpSecurity http) throws Exception {         http             .requestMatchers() // 指定當前`SecurityFilterChain`實例匹配哪些請求                 .anyRequest().and()             .authorizeRequests() // 攔截請求,創建FilterSecurityInterceptor                 .anyRequest().authenticated() // 在創建過濾器的基礎上的一些自定義配置                 .and() // 用and來表示配置過濾器結束,以便進行下一個過濾器的創建和配置             .formLogin().and() // 設置表單登錄,創建UsernamePasswordAuthenticationFilter             .httpBasic(); // basic驗證,創建BasicAuthenticationFilter } 鏈接:https://juejin.im/post/5d9161ace51d4577ff0d9eb7 來源:掘金 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

 

3、http.authorizeRequests()、http.formLogin()、http.httpBasic()分別創建了ExpressionUrlAuthorizationConfigurer,FormLoginConfigurer,HttpBasicConfigurer。在三個類從父級一直往上找,會發現它們都是SecurityConfigurer建造器的子類。SecurityConfigurer中又有configure()方法。該方法被子類實現就用於創建各個過濾器,並將過濾器添加進HttpSecurity中維護的裝有Filter的List中,比如HttpBasicConfigurer中的configure方法,源碼如下:

 

HttpSecurity作爲建造者會把根據api把這些配置器添加到實例中

 

 

 

這些配置器中大都是創建了相應的過濾器,並進行配置,最終在HttpSecurity建造SecurityFilterChain實例時放入過濾器鏈

 

九、認證過濾器 UsernamePasswordAuthenticationFilter

1、參數有username,password的,走UsernamePasswordAuthenticationFilter,提取參數構造UsernamePasswordAuthenticationToken進行認證,成功則填充SecurityContextHolder的Authentication

 

2、UsernamePasswordAuthenticationFilter實現了其父類AbstractAuthenticationProcessingFilter中的attemptAuthentication方法。這個方法會調用認證管理器AuthenticationManager去認證。

 

3、關閉formLogin()驗證後則不通過UsernamePasswordAuthenticationFilter驗證,可自定義攔截器進行驗證攔截

 

十、默認驗證流程

1、開啓formLogin()驗證後,請求會被UsernamePasswordAuthenticationFilter攔截,程序事先在InMemoryUserDetailsManager中保存了一個user在Map<String, MutableUserDetails> users 裏面。攔截器會將兩者進行比對。

2、默認用戶信息是在類SecurityProperties中生成,程序啓動時UserDetailsServiceAutoConfiguration調用inMemoryUserDetailsManager方法,內部調用getOrDeducePassword方法打印出默認的生成的密碼。

 

十一、Spring組件@Scope

1、Bean的作用域

Singleton(單例式):在整個應用中,只創建bean的一個實例。

Prototype(原型式):每次注入或者通過Spring應用上下文獲取的時候,都會創建一個新的bean實例。

Session(會話式):在Web應用中,爲每個會話創建一個bean實例。(eg:電子商務應用中,一個bean代表一個用戶的購物車,只要同一個session一個bean)。

Request(請求式):在Web應用中,爲每個請求創建一個bean實例。

 

鏈接:https://www.cnblogs.com/MrSi/p/7932218.html

 

2、每個客戶端登錄都會產生一個session會話,它的生命週期 從登錄系統到 session過期,期間session上存儲的信息都是有效可用的,我習慣於叫它會話級的緩存,像用戶登錄的身份信息我們一般都會綁定到這個session上。這裏我們要講的@Scope("session"),就是spring提供出來的一個會話級bean方案,在這種模式下,用spring的DI功能來獲取組件,可以做到在會話的生命週期中這個組件只有一個實例。

 

3、接下來再說請求(request),http協議的處理模型,從客戶端發起request請求,到服務端的處理,最後response給客戶端,我們稱爲一次完整的請求。在這樣的一次請求過程中,我們的服務站可能要串行調用funcA->funcB->funcC·... 這樣的一串函數,假如我們的系統需要做細緻的權限校驗(用戶權限,數據權限),更可怕的是funcA,funcB,funcC是3個人分別實現的,而且都要做權限校驗。那麼極有可能會出現3個人各連接了一次數據庫,讀取了同一批權限數據。這裏想象一下,假如一個數據讀取要花2秒,那麼3個方法就要花費6秒的處理時間。但實際上這些數據只用在這個請求過程中讀取一次,緩存在request上下文環境中,我習慣稱之爲線程級緩存。關於線程級緩存java有ThreadLocal方案,像Hibernate的事務就是用這種方案維持一次執行過程中數據庫連接的唯一。當然,今天要講的@Scope("request")也可以做到這種線程級別的緩存。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章