Spring Security過濾器鏈分析-初始化流程(8)

来源:https://www.cnblogs.com/liwenruo/archive/2022/08/07/16550399.html
-Advertisement-
Play Games

過濾器鏈分析 提起Spring Security的實現原理,很多讀者都會想到過濾器鏈。因為Spring Security中的所有功能都是通過過濾器來實現的,這些過濾器組成一個完整的過濾器鏈。那麼,這些過濾器 鏈是如何初始化的?我們前面反覆提到的AuthenticationManager又是如何初始化 ...


過濾器鏈分析

  提起Spring Security的實現原理,很多讀者都會想到過濾器鏈。因為Spring Security中的所有功能都是通過過濾器來實現的,這些過濾器組成一個完整的過濾器鏈。那麼,這些過濾器 鏈是如何初始化的?我們前面反覆提到的AuthenticationManager又是如何初始化的?通過前面章節的學習,相信讀者己經有了一些認識,本章我們將從頭開始,分析Spring Security的初始化流程,同時再通過六個案例來讓讀者深入理解並且學會如何製作過濾器鏈。由於初始化流程相對複雜,因此我們沒有選擇在一開始就講解Spring Security初始化流程,而是放到本節。當讀者對於Spring Security有一個基本的認知之後再來講解,此時相對來說就會比較容易理解。

本章涉及的主要知識點有:

  • 初始化流程分析。
  • ObjectPostProcessor 的使用。
  • 多種用戶定義方式。
  • 定義多個過濾器鏈。
  • 靜態資源過濾。
  • 使用JSON格式登錄。
  • 添加登錄驗證碼。

1. 初始化流程分析

  Spring Security初始化流程整體上來說理解起來並不難,但是這裡涉及許多零碎的知識點, 把這些零碎的知識點搞懂了,再來梳理初始化流程就會容易很多。因此,這裡先介紹一下SpringSecurity中一些常見的關鍵組件,在理解這些組件的基礎上,再來分析初始化流程,就能加深對其的理解。

  1.1 ObjectPostProcessor  

  ObjectPostProcessor是Spring Security中使用頻率最高的組件之一,它是一個對象後置處理器,也就是當一個對象創建成功後,如果還有一些額外的事情需要補充,那麼可以通過 ObjectPostProcessor來進行處理。這個介面中預設只有一個方法postProcess,該方法用來完成對對象的二次處理,代碼如下:

public interface ObjectPostProcessor<T> {
	<O extends T> O postProcess(O object);
}

  ObjectPostProcessor預設有兩個繼承類,如圖4-1所示。

  

圖 4-1

  • AutowireBeanFactoryObjectPostProcessor:由於 Spring Security 中大量採用了 Java 配置, 許多過濾器都是直接new出來的,這些直接new出來的對象並不會自動註入到Spring 容器中。Spring Security這樣做的本意是為了簡化配置,但是卻帶來了另外一個問題就是, 大量new出來的對象需要我們手動註冊到Spring客器中去。AutowireBeanFactoryObjectPostProcessor對象所承擔的就是這件事,一個對象new出來之後,只要調用 AutowireBeanFactoryObjectPostProcessor.postProcess 方法,就可以成功註入到 Spring 容器中,它的實現原理就是通過調用Spring容器中的AutowireCapableBeanFactory對象將一個new出來的對象註入到Spring容器中去。
  • CompositeObjectPostProcessor:這是ObjectPostProcessor 的另一個實現,一個對象可以有一個後置處理器,開發者也可以自定義多個對象後置處理器。 CompositeObjectPostProcessor是一個組合的對象後置處理器,它裡邊維護了一個List集合,集合中存放了某一個對象的所有後置處理器,當需要執行對象的後置處理器時,會遍歷集合中的所有ObjectPostProcessor實例,分別調用實例的postProcess方法進行對象後置處理。在Spring Security框架中,最終使用的對象後置處理器其實就是 CompositeObjectPostProcessor ,它裡邊的集合預設只有一個對象,就是 AutowireBeanFactoryObjectPostProcessor

  在Spring Security中,開發者可以靈活地配置項目中需要哪些Spring Security過濾器,一 旦選定過濾器之後,每一個過濾器都會有一個對應的配置器,叫作xxxConfigurer (例如CorsConfigurer. CsrfConfigurer等),過濾器都是在 xxxConfigurer 中 new 出來的,然後在 postProcess方法中處理一遍,就將這些過濾器註入到Spring容器中了。這是對象後置處理器ObjectPostProcessor的主要作用。

  1.2 SecurityFilterChain

  從名稱上可以看出,SecurityFilterChain就是Spring Security中的過濾器鏈對象。下麵來看一下 SecurityFilterChain的源碼:

public interface SecurityFilterChain {
	boolean matches(HttpServletRequest request);
	List<Filter> getFilters();
}

可以看到,SecurityFilterChain中有兩個方法:

  • matches:該方法用來判斷request請求是否應該被當前過濾器鏈所處理心
  • getFilters:該方法返回一個List集合,集合中存放的就是Spring Security中的過濾器。換言之,如果matches方法返回true,那麼request請求就會在getFilters方法所返回的Filter 集合中被處理。

SecurityFilterChain只有一個預設的實現類就是DefaultSecurityFilterChain,其中定義了兩 個屬性,並具體實現了 SecurityFilterChain中的兩個方法:

查看代碼
 public final class DefaultSecurityFilterChain implements SecurityFilterChain {
	private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
	private final RequestMatcher requestMatcher;
	private final List<Filter> filters;
	public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
		this(requestMatcher, Arrays.asList(filters));
	}
	public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
		logger.info("Creating filter chain: " + requestMatcher + ", " + filters);
		this.requestMatcher = requestMatcher;
		this.filters = new ArrayList<>(filters);
	}
	public RequestMatcher getRequestMatcher() {
		return requestMatcher;
	}
	public List<Filter> getFilters() {
		return filters;
	}
	public boolean matches(HttpServletRequest request) {
		return requestMatcher.matches(request);
	}
	@Override
	public String toString() {
		return "[ " + requestMatcher + ", " + filters + "]";
	}
}

  可以看到,在DefaultSecurityFilterChain的構造方法中,需要傳入兩個對象,一個是請求 匹配器requestMatcher,另一個則是過濾器集合或者過濾器數組filters。這個實現類比較簡單, 這裡就不再贅述了。

  需要註意的是,在一個Spring Security項目中,SecurityFilterChain的實例可能會有多個,在後面的小節中會詳細分析,並演示多個SecurityFilterChain實例的情況。

  1.3 SecurityBuilder

  Spring Security中所有需要構建的對象都可以通過SecurityBuilder來實現,預設的過濾器 鏈、代理過濾器AuthenticationManager 等,都可以通過 SecurityBuilder來構建。SecurityBuilder的實現類如圖4-2所示。

  

圖 4-2

  SecurityBuilder:

  我們先來看SecurityBuilder的源碼:

public interface SecurityBuilder<O> {
	O build() throws Exception;
}

  由上述代碼可以看到,SecurityBuilder中只有一個build方法,就是對象構建方法。build 方法的返回值,就是具體構建的對象泛型O,也就是說不同的SecurityBuilder將來會構建出不同的對象。

  HttpSecurityBuiIder:

  HttpSecurityBuilder是用來構建 HttpSecurity對象的,HttpSecurityBuilder 的定義如下:

public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>> extends
		SecurityBuilder<DefaultSecurityFilterChain> {
	<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(
			Class<C> clazz);
    
	<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(
			Class<C> clazz);
    
	<C> void setSharedObject(Class<C> sharedType, C object);
    
	<C> C getSharedObject(Class<C> sharedType);
    
	H authenticationProvider(AuthenticationProvider authenticationProvider);
    
	H userDetailsService(UserDetailsService userDetailsService) throws Exception;
    
	H addFilterAfter(Filter filter, Class<? extends Filter> afterFilter);
    
	H addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter);
    
	H addFilter(Filter filter);
}

我們簡單分析一下這段源碼:

  • HttpSecurityBuilder對象本身在定義時就有一個泛型,這個泛型是HttpSecurityBuilder 的子類,由於預設情況下HttpSecurityBuilder的實現類只有一個HttpSecurity,所以可以暫且把介面中的H都當成HttpSecurity來理解。
  • HttpSecurityBuilder 繼承自 SecurityBuilder 介面,同時也指定了 SecurityBuilder 中的泛型為DefaultSecurityFilterChain,也就是說,HttpSecurityBuilder最終想要構建的對象是 DefaultSecurityFilterChain
  • getConfigurer方法用來獲取一個配置器,所謂的配置器就是xxxConfigurer,我們將在下一小節中詳細介紹配置器,
  • removeConfigurer方法用來移除一個配置器(相當於從Spring Security過濾器鏈中移 除一個過濾器)。
  • setSharedObject/getSharedObject這兩個方法用來設置或者獲取一個可以在多個配置器之間共用的對象。
  • authenticationProvider 方法可以用來配置一個認證器 AuthenticationProvider
  • userDetailsService 方法可以用來配置一個數據源 UserDetailsService
  • addFilterAfter/addFilterBefore方法表示在某一個過濾器之後或者之前添加一個自定義的過濾器。
  • addFilter方法可以添加一個過濾器,這個過濾器必須是Spring Security框架提供的 過濾器的一個實例或者其擴展,添加完成後,會自動進行過濾器的排序。

AbstractSecurityBuilder:

  AbstractSecurityBuilder實現了 SecurityBuilder 介面,並對 build 做了完善,確保只 build 一次。我們來看—下 AbstractSecurityBuilder 源碼:

public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
	private AtomicBoolean building = new AtomicBoolean();

	private O object;

	public final O build() throws Exception {
		if (this.building.compareAndSet(false, true)) {
			this.object = doBuild();
			return this.object;
		}
		throw new AlreadyBuiltException("This object has already been built");
	}

	public final O getObject() {
		if (!this.building.get()) {
			throw new IllegalStateException("This object has not been built");
		}
		return this.object;
	}

	protected abstract O doBuild() throws Exception;
}

由上述代碼可以看到,在AbstractSecurityBuilder類中:

  • 首先聲明瞭 building變數,可以確保即使在多線程環境下,配置類也只構建一次。
  • build方法進行重寫,並II設置為final,這樣在AbstractSecurityBuilder的了類中 將不能再次重寫build方法,在build方法內部,通過building變數來控制配置類只構建一次, 具體的構建工作則交給doBuild方法去完成。
  • getObject方法用來返回構建的對象。
  • doBuild方法則是具體的構建方法,該方法在AbstractSecurityBuilder中是一個抽象方法,具體的實現在其子類中。

一言以蔽之,AbstractSecurityBuilder的作用是確保目標對象只被構建一次。

  AbstractConfiguredSecurityBuilder:

  AbstractConfiguredSecurityBuilder類的源碼就稍微長一點,我們分別來看,首先在AbstractConfiguredSecurityBuilder中聲明瞭一個枚舉類,用來描述構建過程的不同狀態:

private static enum BuildState {
    UNBUILT(0),
    INITIALIZING(1),
    CONFIGURING(2),
    BUILDING(3),
    BUILT(4);
    private final int order;
    BuildState(int order) {
        this.order = order;
    }
    public boolean isInitializing() {
        return INITIALIZING.order == order;
    }
    public boolean isConfigured() {
        return order >= CONFIGURING.order;
    }
}

可以看到,整個構建過程一共有五種不同的狀態:

  • UNBUILT:配置類構建前。
  • INITIALIZING:初始化中(初始化完成之前是這個狀態)。
  • CONFIGURING:配置中(開始構建之前是這個狀態)。
  • BUILDING:構建中。
  • BUILT:構建完成。

這個枚舉類裡邊還提供了兩個判斷方法 islnitializing 表示是否正在初始化中, isConfigured 方法表示是否已完成配置。

AbstractConfiguredSecurityBuilder中還聲明瞭 configurers變數,用來保存所有的配置類。針對configurers變數,我們可以進行添加配置、移除配置等操作,相關方法如下:

  

查看代碼
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
		extends AbstractSecurityBuilder<O> {
	private final Log logger = LogFactory.getLog(getClass());
	private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();
	private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing = new ArrayList<SecurityConfigurer<O, B>>();
	private final Map<Class<? extends Object>, Object> sharedObjects = new HashMap<Class<? extends Object>, Object>();
	private final boolean allowConfigurersOfSameType;
	private BuildState buildState = BuildState.UNBUILT;
	private ObjectPostProcessor<Object> objectPostProcessor;
	protected AbstractConfiguredSecurityBuilder(
			ObjectPostProcessor<Object> objectPostProcessor) {
		this(objectPostProcessor, false);
	}
	protected AbstractConfiguredSecurityBuilder(
			ObjectPostProcessor<Object> objectPostProcessor,
			boolean allowConfigurersOfSameType) {
		Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null");
		this.objectPostProcessor = objectPostProcessor;
		this.allowConfigurersOfSameType = allowConfigurersOfSameType;
	}
	public O getOrBuild() {
		if (isUnbuilt()) {
			try {
				return build();
			}
			catch (Exception e) {
				logger.debug("Failed to perform build. Returning null", e);
				return null;
			}
		}
		else {
			return getObject();
		}
	}
	@SuppressWarnings("unchecked")
	public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
			throws Exception {
		configurer.addObjectPostProcessor(objectPostProcessor);
		configurer.setBuilder((B) this);
		add(configurer);
		return configurer;
	}
	public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
		add(configurer);
		return configurer;
	}
	@SuppressWarnings("unchecked")
	public <C> void setSharedObject(Class<C> sharedType, C object) {
		this.sharedObjects.put(sharedType, object);
	}
	@SuppressWarnings("unchecked")
	public <C> C getSharedObject(Class<C> sharedType) {
		return (C) this.sharedObjects.get(sharedType);
	}
	public Map<Class<? extends Object>, Object> getSharedObjects() {
		return Collections.unmodifiableMap(this.sharedObjects);
	}
	@SuppressWarnings("unchecked")
	private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
		Assert.notNull(configurer, "configurer cannot be null");

		Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
				.getClass();
		synchronized (configurers) {
			if (buildState.isConfigured()) {
				throw new IllegalStateException("Cannot apply " + configurer
						+ " to already built object");
			}
			List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
					.get(clazz) : null;
			if (configs == null) {
				configs = new ArrayList<SecurityConfigurer<O, B>>(1);
			}
			configs.add(configurer);
			this.configurers.put(clazz, configs);
			if (buildState.isInitializing()) {
				this.configurersAddedInInitializing.add(configurer);
			}
		}
	}
	@SuppressWarnings("unchecked")
	public <C extends SecurityConfigurer<O, B>> List<C> getConfigurers(Class<C> clazz) {
		List<C> configs = (List<C>) this.configurers.get(clazz);
		if (configs == null) {
			return new ArrayList<>();
		}
		return new ArrayList<>(configs);
	}
	@SuppressWarnings("unchecked")
	public <C extends SecurityConfigurer<O, B>> List<C> removeConfigurers(Class<C> clazz) {
		List<C> configs = (List<C>) this.configurers.remove(clazz);
		if (configs == null) {
			return new ArrayList<>();
		}
		return new ArrayList<>(configs);
	}
	@SuppressWarnings("unchecked")
	public <C extends SecurityConfigurer<O, B>> C getConfigurer(Class<C> clazz) {
		List<SecurityConfigurer<O, B>> configs = this.configurers.get(clazz);
		if (configs == null) {
			return null;
		}
		if (configs.size() != 1) {
			throw new IllegalStateException("Only one configurer expected for type "
					+ clazz + ", but got " + configs);
		}
		return (C) configs.get(0);
	}
	@SuppressWarnings("unchecked")
	public <C extends SecurityConfigurer<O, B>> C removeConfigurer(Class<C> clazz) {
		List<SecurityConfigurer<O, B>> configs = this.configurers.remove(clazz);
		if (configs == null) {
			return null;
		}
		if (configs.size() != 1) {
			throw new IllegalStateException("Only one configurer expected for type "
					+ clazz + ", but got " + configs);
		}
		return (C) configs.get(0);
	}
	@SuppressWarnings("unchecked")
	public O objectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
		Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null");
		this.objectPostProcessor = objectPostProcessor;
		return (O) this;
	}
	protected <P> P postProcess(P object) {
		return this.objectPostProcessor.postProcess(object);
	}
}

我們解析一下這段源碼:

  • 首先聲明瞭一個configurers變數,用來保存所有的配置類,key是配置類Class對象, 值是一個List集合中放著配置類。
  • apply方法有兩個,參數類型略有差異,主要功能基本一致,都是向configurers變數中添加配置類,具體的添加過程則是調用add方法。
  • add方法用來將所有的配置類保存到configurers中,在添加的過程中,如果 allowConfigurersOfSameType變數為true,則表示允許相同類型的配置類存在,也就是List集合中可以存在多個相同類型的配置類。預設情況下,如果是普通配置類, allowConfigurersOfSameTypefalse,所以List集合中的配置類始終只有一個配置類;如果在 AuthenticationManagerBuilder 中設置 allowConfigurersOfSameType true,此時相同類型的配置類可以有多個(下文會詳細分析AuthenticationManagerBuilder)。
  • getConfigurers(Class<C>)方法可以從configurers中返回某一個配置類對應的所有實例
  • removeConfigurers方法可以從configurers中移除某一個配置類對應的所有實例,並返回被移除掉的配置類實例集合,
  • getConfigurer方法也是獲取配置類實例,但是只獲取集合中第一項。
  • removeConfigurer方法可以從configurers中移除某一個配置類對應的所有配置類實例,並返回被移除掉的配置類實例中的第一項。
  • getConfigurers方法是一個私有方法,主要是把所有的配置類實例放到一個集合中返 回.在配置類初始化和配置的時候,會調用到該方法,

這些就是 AbstractConfiguredSecurityBuilder 中關於 configurers 的所有操作。

接下來就是AbstractConfiguredSecurityBuilder中的doBuild方法了,這是核心的構建方法。

查看代碼
 @Override
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;
    }
}
protected void beforeInit() throws Exception {
}
protected void beforeConfigure() throws Exception {
}
protected abstract O performBuild() throws Exception;
@SuppressWarnings("unchecked")
private void init() throws Exception {
    Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
    for (SecurityConfigurer<O, B> configurer : configurers) {
        configurer.init((B) this);
    }
    for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
        configurer.init((B) this);
    }
}
@SuppressWarnings("unchecked")
private void configure() throws Exception {
    Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
    for (SecurityConfigurer<O, B> configurer : configurers) {
        configurer.configure((B) this);
    }
}
private Collection<SecurityConfigurer<O, B>> getConfigurers() {
    List<SecurityConfigurer<O, B>> result = new ArrayList<SecurityConfigurer<O, B>>();
    for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
        result.addAll(configs);
    }
    return result;
}
private boolean isUnbuilt() {
    synchronized (configurers) {
        return buildState == BuildState.UNBUILT;
    }
}
  • doBuild方法中,一邊更新構建狀態,一邊執行構建方法由構建方法中,beforeInit 是一個空的初始化方法,如果需要在初始化之前做一些準備工作,可以通過重寫該方法實現,
  • init方法是所有配置類的初始化方法,在該方法中,遍歷所有的配置類,並調用其 init方法完成初始化操作。
  • beforeConfigure方法可以在configure方法執行之前做一些準備操作心該方法預設也是一個空方法,
  • configure方法用來完成所有配置類的配置,在configure方法中,遍歷所有的配置類,分別調用其configure方法完成配置。
  • performBuild方法用來做最終的構建操作,前面的準備工作完成後,最後在 performBuild方法中完成構建,這是一個抽象方法,具體的實現則在不同的配置類中。

    這些就是AbstractConfiguredSecurityBuilder中最主要的幾個方法,其他一些方法比較簡單,這裡就不一一贅述了

ProviderManagerBuilder:

  ProviderManagerBuilder繼承自 SecurityBuilder介面,並制定了構建的對象是 AuthenticationManager, 代碼如下:

public interface ProviderManagerBuilder<B extends ProviderManagerBuilder<B>> extends
		SecurityBuilder<AuthenticationManager> {
	B authenticationProvider(AuthenticationProvider authenticationProvider);
}

  可以看到,ProviderManagerBuilder 中增加 了一個 authenticationProvider 方法,同時通過泛型指定了構建的對象為AuthenticationManager。

AuthenticationManagerBuilder:

  AuthenticationManagerBuilder 用來構建 AuthenticationManager 對象,它繼承自 AbstractConfiguredSecurityBuilder,並且實現了 ProviderManagerBuilder介面,源碼比較長,我們截取部分常用代碼,代碼如下:

  

查看代碼
 public class AuthenticationManagerBuilder
		extends
		AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
		implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
	private final Log logger = LogFactory.getLog(getClass());
	private AuthenticationManager parentAuthenticationManager;
	private List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
	private UserDetailsService defaultUserDetailsService;
	private Boolean eraseCredentials;
	private AuthenticationEventPublisher eventPublisher;

	public AuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
		super(objectPostProcessor, true);
	}

	public AuthenticationManagerBuilder parentAuthenticationManager(
			AuthenticationManager authenticationManager) {
		if (authenticationManager instanceof ProviderManager) {
			eraseCredentials(((ProviderManager) authenticationManager)
					.isEraseCredentialsAfterAuthentication());
		}
		this.parentAuthenticationManager = authenticationManager;
		return this;
	}

	public AuthenticationManagerBuilder authenticationEventPublisher(
			AuthenticationEventPublisher eventPublisher) {
		Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
		this.eventPublisher = eventPublisher;
		return this;
	}

	public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
		this.eraseCredentials = eraseCredentials;
		return this;
	}

	public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
			throws Exception {
		return apply(new InMemoryUserDetailsManagerConfigurer<>());
	}

	public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
			throws Exception {
		return apply(new JdbcUserDetailsManagerConfigurer<>());
	}

	public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
			T userDetailsService) throws Exception {
		this.defaultUserDetailsService = userDetailsService;
		return apply(new DaoAuthenticationConfigurer<>(
				userDetailsService));
	}

	public LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthentication()
			throws Exception {
		return apply(new LdapAuthenticationProviderConfigurer<>());
	}

	public AuthenticationManagerBuilder authenticationProvider(
			AuthenticationProvider authenticationProvider) {
		this.authenticationProviders.add(authenticationProvider);
		return this;
	}

	@Override
	protected ProviderManager performBuild() throws Exception {
		if (!isConfigured()) {
			logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
			return null;
		}
		ProviderManager providerManager = new ProviderManager(authenticationProviders,
				parentAuthenticationManager);
		if (eraseCredentials != null) {
			providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
		}
		if (eventPublisher != null) {
			providerManager.setAuthenticationEventPublisher(eventPublisher);
		}
		providerManager = postProcess(providerManager);
		return providerManager;
	}

	public boolean isConfigured() {
		return !authenticationProviders.isEmpty() || parentAuthenticationManager != null;
	}

	public UserDetailsService getDefaultUserDetailsService() {
		return this.defaultUserDetailsService;
	}

	private <C extends UserDetailsAwareConfigurer<AuthenticationManagerBuilder, ? extends UserDetailsService>> C apply(
			C configurer) throws Exception {
		this.defaultUserDetailsService = configurer.getUserDetailsService();
		return (C) super.apply(configurer);
	}
}
  • 首先在AuthenticationManagerBuilder的構造方法中,調用了父類的構造方法,註意第二個參數傳遞了true,表示允許相同類型的配置類同時存在(結合 AbstractConfiguredSecurityBuilder 的源碼來理解)
  • parentAuthenticationManager 方法用來給一個 AuthenticationManager 設置 parents
  • inMemoryAuthentication方法用來配置基於記憶體的數據源,該方法會自動創建 InMemoryUserDetailsManagerConfigurer配置類,並最終將該配置類添加到父類的configurers 變數中。由於設置了允許相同類型的配置類同時存在,因此inMemoryAuthentication方法可以反覆調用多次
  • jdbcAuthentication 以及 userDetailsService 方法與 inMemoryAuthentication 方法類似, 也是用來配置數據源的,這裡不再贅述。
  • authenticationProvider 方法用來向 authenticationProviders 集合中添加 AuthenticationProvider對象,根據前面第3節的介紹,我們己經知道一個AuthenticationManager實例中包含多個 AuthenticationProvider 實例,那麼多個 AuthenticationProvider 實例可以通過 authenticationProvider方法進行添加。
  • performBuild方法則執行具體的構建工作,常用的AuthenticationManager實例就是 ProviderManager,所以這裡創建 ProviderManager 對象,並旦配置 authenticationProviders parentAuthenticationManager對象,ProviderManager對象創建成功之後,再去對象後置處理器中處理一遍再返回。

這就是AuthenticationManagerBuilder中的一個大致邏輯。

HttpSecurity:

  HttpSecurity的主要作用是用來構建一條過濾器鏈,並反映到代碼上,也就是構建一個 DefaultSecurityFilterChain 對象,一個 DefaultSecurityFilterChain 對象包含一個路徑匹配器和多個Spring Security 過濾器,HttpSecurity 中通過收集各種各樣的 xxxConfigurer,將 Spring Security 過濾器對應的配置類收集起來,並保存到父類AbstractConfiguredSecurityBuilderconfigurers 變數中,在後續的構建過程中,再將這些xxxConfigurer構建為具體的Spring Security過濾器, 同時添加到HttpSecurityfilters對象中。

  由於HttpSecurity中存在大量功能類似的方法,因此這裡挑選一個作為例子用來說明HttpSecurity的配置原理,代碼如下:

  

查看代碼
 public final class HttpSecurity extends
		AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
		implements SecurityBuilder<DefaultSecurityFilterChain>,
		HttpSecurityBuilder<HttpSecurity> {
	private final RequestMatcherConfigurer requestMatcherConfigurer;
	private List<Filter> filters = new ArrayList<>();
	private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
	private FilterComparator comparator = new FilterComparator();

	@SuppressWarnings("unchecked")
	public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor,
			AuthenticationManagerBuilder authenticationBuilder,
			Map<Class<? extends Object>, Object> sharedObjects) {
		super(objectPostProcessor);
		Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
		setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
		for (Map.Entry<Class<? extends Object>, Object> entry : sharedObjects
				.entrySet()) {
			setSharedObject((Class<Object>) entry.getKey(), entry.getValue());
		}
		ApplicationContext context = (ApplicationContext) sharedObjects
				.get(ApplicationContext.class);
		this.requestMatcherConfigurer = new RequestMatcherConfigurer(context);
	}

	private ApplicationContext getContext() {
		return getSharedObject(ApplicationContext.class);
	}

	public OpenIDLoginConfigurer<HttpSecurity> openidLogin() throws Exception {
		return getOrApply(new OpenIDLoginConfigurer<>());
	}

	public HeadersConfigurer<HttpSecurity> headers() throws Exception {
		return getOrApply(new HeadersConfigurer<>());
	}

	public CorsConfigurer<HttpSecurity> cors() throws Exception {
		return getOrApply(new CorsConfigurer<>());
	}

	public SessionManagementConfigurer<HttpSecurity> sessionManagement() throws Exception {
		return getOrApply(new SessionManagementConfigurer<>());
	}

	public PortMapperConfigurer<HttpSecurity> portMapper() throws Exception {
		return getOrApply(new PortMapperConfigurer<>());
	}

	public JeeConfigurer<HttpSecurity> jee() throws Exception {
		return getOrApply(new JeeConfigurer<>());
	}

	public X509Configurer<HttpSecurity> x509() throws Exception {
		return getOrApply(new X509Configurer<>());
	}

	public RememberMeConfigurer<HttpSecurity> rememberMe() throws Exception {
		return getOrApply(new RememberMeConfigurer<>());
	}

	public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
			throws Exception {
		ApplicationContext context = getContext();
		return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context))
				.getRegistry();
	}

	public RequestCacheConfigurer<HttpSecurity> requestCache() throws Exception {
		return getOrApply(new RequestCacheConfigurer<>());
	}

	public ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling() throws Exception {
		return getOrApply(new ExceptionHandlingConfigurer<>());
	}

	public SecurityContextConfigurer<HttpSecurity> securityContext() throws Exception {
		return getOrApply(new SecurityContextConfigurer<>());
	}

	public ServletApiConfigurer<HttpSecurity> servletApi() throws Exception {
		return getOrApply(new ServletApiConfigurer<>());
	}

	public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
		ApplicationContext context = getContext();
		return getOrApply(new CsrfConfigurer<>(context));
	}

	public LogoutConfigurer<HttpSecurity> logout() throws Exception {
		return getOrApply(new LogoutConfigurer<>());
	}

	public AnonymousConfigurer<HttpSecurity> anonymous() throws Exception {
		return getOrApply(new AnonymousConfigurer<>());
	}

	public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
		return getOrApply(new FormLoginConfigurer<>());
	}

	public OAuth2LoginConfigurer<HttpSecurity> oauth2Login() throws Exception {
		return getOrApply(new OAuth2LoginConfigurer<>());
	}

	public ChannelSecurityConfigurer<HttpSecurity>.ChannelRequestMatcherRegistry requiresChannel()
			throws Exception {
		ApplicationContext context = getContext();
		return getOrApply(new ChannelSecurityConfigurer<>(context))
				.getRegistry();
	}

	public HttpBasicConfigurer<HttpSecurity> httpBasic() throws Exception {
		return getOrApply(new HttpBasicConfigurer<>());
	}

	public <C> void setSharedObject(Class<C> sharedType, C object) {
		super.setSharedObject(sharedType, object);
	}

	@Override
	protected void beforeConfigure() throws Exception {
		setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
	}

	@Override
	protected DefaultSecurityFilterChain performBuild() throws Exception {
		Collections.sort(filters, comparator);
		return new DefaultSecurityFilterChain(requestMatcher, filters);
	}

	public HttpSecurity authenticationProvider(
			AuthenticationProvider authenticationProvider) {
		getAuthenticationRegistry().authenticationProvider(authenticationProvider);
		return this;
	}

	public HttpSecurity userDetailsService(UserDetailsService userDetailsService)
			throws Exception {
		getAuthenticationRegistry().userDetailsService(userDetailsService);
		return this;
	}

	private AuthenticationManagerBuilder getAuthenticationRegistry() {
		return getSharedObject(AuthenticationManagerBuilder.class);
	}

	public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
		comparator.registerAfter(filter.getClass(), afterFilter);
		return addFilter(filter);
	}

	public HttpSecurity addFilterBefore(Filter filter,
			Class<? extends Filter> beforeFilter) {
		comparator.registerBefore(filter.getClass(), beforeFilter);
		return addFilter(filter);
	}

	public HttpSecurity addFilter(Filter filter) {
		Class<? extends Filter> filterClass = filter.getClass();
		if (!comparator.isRegistered(filterClass)) {
			throw new IllegalArgumentException(
					"The Filter class "
							+ filterClass.getName()
							+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
		}
		this.filters.add(filter);
		return this;
	}

	public HttpSecurity addFilterAt(Filter filter, Class<? extends Filter> atFilter) {
		this.comparator.registerAt(filter.getClass(), atFilter);
		return addFilter(filter);
	}

	public RequestMatcherConfigurer requestMatchers() {
		return requestMatcherConfigurer;
	}

	public HttpSecurity requestMatcher(RequestMatcher requestMatcher) {
		this.requestMatcher = requestMatcher;
		return this;
	}

	public HttpSecurity antMatcher(String antPattern) {
		return requestMatcher(new AntPathRequestMatcher(antPattern));
	}

	public HttpSecurity mvcMatcher(String mvcPattern) {
		HandlerMappingIntrospector introspector = new HandlerMappingIntrospector(getContext());
		return requestMatcher(new MvcRequestMatcher(introspector, mvcPattern));
	}

	public HttpSecurity regexMatcher(String pattern) {
		return requestMatcher(new RegexRequestMatcher(pattern, null));
	}

	public final class MvcMatchersRequestMatcherConfigurer extends RequestMatcherConfigurer {

		private MvcMatchersRequestMatcherConfigurer(ApplicationContext context,
				List<MvcRequestMatcher> matchers) {
			super(context);
			this.matchers = new ArrayList<>(matchers);
		}

		public RequestMatcherConfigurer servletPath(String servletPath) {
			for (RequestMatcher matcher : this.matchers) {
				((MvcRequestMatcher) matcher).setServletPath(servletPath);
			}
			return this;
		}

	}

	public class RequestMatcherConfigurer
			extends AbstractRequestMatcherRegistry<RequestMatcherConfigurer> {

		protected List<RequestMatcher> matchers = new ArrayList<>();

		private RequestMatcherConfigurer(ApplicationContext context) {
			setApplicationContext(context);
		}

		@Override
		public MvcMatchersRequestMatcherConfigurer mvcMatchers(HttpMethod method,
				String... mvcPatterns) {
			List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
			setMatchers(mvcMatchers);
			return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers);
		}

		@Override
		public MvcMatchersRequestMatcherConfigurer mvcMatchers(String... patterns) {
			return mvcMatchers(null, patterns);
		}

		@Override
		protected RequestMatcherConfigurer chainRequestMatchers(
				List<RequestMatcher> requestMatchers) {
			setMatchers(requestMatchers);
			return this;
		}

		private void setMatchers(List<? extends RequestMatcher> requestMatchers) {
			this.matchers.addAll(requestMatchers);
			requestMatcher(new OrRequestMatcher(this.matchers));
		}

		public HttpSecurity and() {
			return HttpSecurity.this;
		}

	}

	@SuppressWarnings("unchecked")
	private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
			C configurer) throws Exception {
		C existingConfig = (C) getConfigurer(configurer.getClass());
		if (existingConfig != null) {
			return existingConfig;
		}
		return apply(configurer);
	}
}
  • form表單登錄配置為例,在HttpSecurity中有兩個重載方法可以進行配置:第一個是一個無參的formLogin方法,該方法的返回值是一個FormLoginConfigurer<HttpSecurity> 對象,開發者可以在該對象的基礎上繼續完善對form表單的配置,我們在前面章節中配置的表單登錄都是通過這種方式來進行配置的。第二個是一個有參的formLogin方法,該方法的參數是一個FormLoginConfigurer對象,返回值則是一個HttpSecurity對象,也就是說開發者可以提前在外面配置好FormLoginConfigurer對象,然後直接傳進來進行配置即可,返回值 HttpSecurity對象則可以在方法返回後直接進行其他過濾器的配置。無論是有參還是無參,最終都會調用到getOrApply方法,該方法會調用父類的getConfigurer方法去查看是否己經有對應的配置類了,如果有,則直接返回;如果沒有,則調用apply方法添加到父類的configurer變數中 HttpSecurity 中其他過濾器的配置都和form表單登錄配置類似,這裡就不再贅述了。
  • 每一套過濾器鏈都會有一個AuthenticationManager對象來進行認證操作(如果認證失敗,則會調用AuthenticationManagerparent再次進行認證),主要是通過authenticationProvider方法配置執行認證的authenticationProvider對象,通過userDetailsService方法配置 UserDetailsService,最後在 beforeConfigure 方法中觸發 AuthenticationManager 對象的構建。
  • performBuild方法則是進行DefaultSecurityFilterChain對象的構建,傳入請求匹配器和過濾器集合filters,在構建之前,會先按照既定的順序對filters進行排序。
  • 通過addFilterAfteraddFilterBefore兩個方法,我們可以在某一個過濾器之後或者之前添加一個自定義的過濾器(該方法巳在HttpSecurityBuilder中聲明,此處是具體實現)。
  • addFilter方法可以向過濾器鏈中添加一個過濾器,這個過濾器必須是Spring Security 框架提供的過濾器的一個實例或者其擴展,實際上,在每一個xxxConfigurerconfigure方法中,都會調用addFilter方法將構建好的過濾器添加到HttpSecurity中的filters集合中(addFilter 方法已在HttpSecurityBuilder中聲明,此處是具體實現)。
  • addFilterAt方法可以在指定位置添加一個過濾器。需要註意的是,在同一個位置添加多個過濾器並不會覆蓋現有的過濾器。

  這便是HttpSecurity的基本功能。

WebSecurity:

  相比於HttpSecurity, WebSecurity是在一個更大的層面上去構建過濾器。一個HttpSecurity 對象可以構建一個過濾器鏈,也就是一個DefaultSecurityFilterChain對象,而一個項目中可以存在多個HttpSecurity對象,也就可以構建多個DefaultSecurityFilterChain過濾器鏈。

  WebSecurity 負責將 HttpSecurity 所構建的 DefaultSecurityFilterChain 對象(可能有多個), 以及其他一些需要忽略的請求,再次重新構建為一個FilterChainProxy對象,同時添加上HTTP 防火牆,

  我們來看一下WebSecurity中的幾個關鍵方法:

查看代碼
 public final class WebSecurity extends
		AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
		SecurityBuilder<Filter>, ApplicationContextAware {
	private final Log logger = LogFactory.getLog(getClass());

	private final List<RequestMatcher> ignoredRequests = new ArrayList<>();

	private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>();

	private IgnoredRequestConfigurer ignoredRequestRegistry;

	private FilterSecurityInterceptor filterSecurityInterceptor;

	private HttpFirewall httpFirewall;

	private boolean debugEnabled;

	private WebInvocationPrivilegeEvaluator privilegeEvaluator;

	private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();

	private SecurityExpressionHandler<FilterInvocation> expressionHandler = defaultWebSecurityExpressionHandler;

	private Runnable postBuildAction = new Runnable() {
		public void run() {
		}
	};

	public WebSecurity(ObjectPostProcessor<Object> objectPostProcessor) {
		super(objectPostProcessor);
	}

	public IgnoredRequestConfigurer ignoring() {
		return ignoredRequestRegistry;
	}

	public WebSecurity httpFirewall(HttpFirewall httpFirewall) {
		this.httpFirewall = httpFirewall;
		return this;
	}

	public WebSecurity debug(boolean debugEnabled) {
		this.debugEnabled = debugEnabled;
		return this;
	}

	public WebSecurity addSecurityFilterChainBuilder(
			SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
		this.securityFilterChainBuilders.add(securityFilterChainBuilder);
		return this;
	}

	public WebSecurity privilegeEvaluator(
			WebInvocationPrivilegeEvaluator privilegeEvaluator) {
		this.privilegeEvaluator = privilegeEvaluator;
		return this;
	}

	public WebSecurity expressionHandler(
			SecurityExpressionHandler<FilterInvocation> expressionHandler) {
		Assert.notNull(expressionHandler, "expressionHandler cannot be null");
		this.expressionHandler = expressionHandler;
		return this;
	}

	public SecurityExpressionHandler<FilterInvocation> getExpressionHandler() {
		return expressionHandler;
	}

	public WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() {
		if (privilegeEvaluator != null) {
			return privilegeEvaluator;
		}
		return filterSecurityInterceptor == null ? null
				: new DefaultWebInvocationPrivilegeEvaluator(filterSecurityInterceptor);
	}

	public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
		this.filterSecurityInterceptor = securityInterceptor;
		return this;
	}

	public WebSecurity postBuildAction(Runnable postBuildAction) {
		this.postBuildAction = postBuildAction;
		return this;
	}

	@Override
	protected Filter performBuild() throws Exception {
		Assert.state(
				!securityFilterChainBuilders.isEmpty(),
				"At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "
						+ WebSecurity.class.getSimpleName()
						+ ".addSecurityFilterChainBuilder directly");
		int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
				chainSize);
		for (RequestMatcher ignoredRequest : ignoredRequests) {
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		if (httpFirewall != null) {
			filterChainProxy.setFirewall(httpFirewall);
		}
		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;
		if (debugEnabled) {
			logger.warn("\n\n"
					+ "********************************************************************\n"
					+ "**********        Security debugging is enabled.       *************\n"
					+ "**********    This may include sensitive information.  *************\n"
					+ "**********      Do not use in a production system!     *************\n"
					+ "********************************************************************\n\n");
			result = new DebugFilter(filterChainProxy);
		}
		postBuildAction.run();
		return result;
	}

	public final class MvcMatchersIgnoredRequestConfigurer
			extends IgnoredRequestConfigurer {
		private final List<MvcRequestMatcher> mvcMatchers;

		private MvcMatchersIgnoredRequestConfigurer(ApplicationContext context,
				List<MvcRequestMatcher> mvcMatchers) {
			super(context);
			this.mvcMatchers = mvcMatchers;
		}

		public IgnoredRequestConfigurer servletPath(String servletPath) {
			for (MvcRequestMatcher matcher : this.mvcMatchers) {
				matcher.setServletPath(servletPath);
			}
			return this;
		}
	}

	public class IgnoredRequestConfigurer
			extends AbstractRequestMatcherRegistry<IgnoredRequestConfigurer> {

		private IgnoredRequestConfigurer(ApplicationContext context) {
			setApplicationContext(context);
		}

		@Override
		public MvcMatchersIgnoredRequestConfigurer mvcMatchers(HttpMethod method,
				String... mvcPatterns) {
			List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
			WebSecurity.this.ignoredRequests.addAll(mvcMatchers);
			return new MvcMatchersIgnoredRequestConfigurer(getApplicationContext(),
					mvcMatchers);
		}

		@Override
		public MvcMatchersIgnoredRequestConfigurer mvcMatchers(String... mvcPatterns) {
			return mvcMatchers(null, mvcPatterns);
		}

		@Override
		protected IgnoredRequestConfigurer chainRequestMatchers(
				List<RequestMatcher> requestMatchers) {
			WebSecurity.this.ignoredRequests.addAll(requestMatchers);
			return this;
		}

		public WebSecurity and() {
			return WebSecurity.this;
		}
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.defaultWebSecurityExpressionHandler
				.setApplicationContext(applicationContext);
		this.ignoredRequestRegistry = new IgnoredRequestConfigurer(applicationContext);
	}
}

   

  • 首先在WebSecurity中聲明瞭 ignoredRequests集合,這個集合中保存了所有被忽略的請求,因為在實際項目中,並非所有的請求都需要經過Spring Security過濾器鏈,有一些靜態資源可能不需要許可權認證,直接返回給客戶端即可,那麼這些需要忽略的請求可以直接保存 在 ignoredRequests 變數中。
  • 接下來聲明瞭一個securityFilterChainBuilders集合,該集合用來保存所有的 HttpSecurity 對象,每一個 HttpSecurity 對象創建成功之後,通過 addSecurityFliterChainBuilder 方法將 HttpSecurity 對象添加到 securityFilterChainBuilders 集合中。
  • httpFirewall方法可以用來配置請求防火牆,關於請求防火牆,我們會在後面的章節中專門講解。
  • performBuild方法則是具體的構建方法,在該方法中,首先統計出過濾器鏈的總個數(被忽略的請求個數+通過HttpSecurity創建出來的過濾器鏈個數),然後創建一個集合 securityFilterChains,遍歷被忽略的請求並分別構建成DefaultSecurityFilterChain對象保存到 securityFilterChains集合中。需要註意的是,對於被忽略的請求,在構建DefaultSecurityFilterChain對象時,只是傳入了請求匹配器,而沒有傳入對應的過濾器鏈,這就意味著這些被忽略掉的請求,將來不必經過Spring Security過濾器鏈;接下來再遍歷securityFilterChainBuilders集合,調用每個對象的build方法構建DefaultSecurityFilterChain並存入securityFilterChains集合中,然後傳入securityFilterChains集合構建FilterChainProxy對象,最後再設置HTTP 防火牆。所有設置完成之後,最後返回filterChainProxy對象。

  FilterChainProxy就是我們最終構建出來的代理過濾器鏈,通過Spring提供的DelegatingFilterProxyFilterChainProxy對象嵌入到WebFilter中(原生過濾器鏈中)。

  讀者可以回憶一下前面我們繪製的FilterChainProxy架構圖,對照著來理解上面的源碼應該就很容易了,如圖4-3所示。

  至此,關於SecurityBuilder體系中的幾個關鍵類就介紹完了 ,至於HttpSecurityWebSecurity是怎麼配置到一起的,我們將在後面的章節中進行分析。

  

圖 3-4

  1.4 FilterChainProxy

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 精華筆記: 什麼是類?什麼是對象? 現實生活中是由很多很多對象組成的,基於對象抽出了類 對象:軟體中真實存在的單個個體/東西 類:類型/類別,代表一類個體 類是對象的模板/模子,對象是類的具體的實例 類中可以包含: 對象的屬性/特征 成員變數 對象的行為/動作/功能 方法 一個類可以創建多個對象 如 ...
  • doc或docx(word)或image類型文件批量轉PDF腳本 1.實際生產環境中遇到文件展示只能適配PDF版本的文件,奈何一萬個文件有七千個都是word或者image類型的,由此搞個腳本批量轉換下上傳至OSS,為前端提供數據支撐。 2.環境準備,這裡使用的是aspose-words-18.6-j ...
  • 一、菜單 功能描述:顯示簡單的菜單,供用戶選擇操作 實現步驟:直接cout輸出 二、退出功能 功能描述:根據用戶不同的操作代碼選擇,進入不同的功能,我們使用switch分支結構進行搭建 實現步驟:用while(ture)迴圈包涵switch, case 0:時用return 0 ,退出迴圈,即退出通 ...
  • 序列類型 字元串 由很多個字元組成的字元序列,字元串屬於 **序列類型 序列簡介 數值類型:可以表示 數字,數值 int float bool 序列類型:存儲多個數據的一種數據類型 str : 可以存儲數字,字母,特殊符號,中文等數據.表現形式為 一對引號包囊起來的數據 list 列表 tuple ...
  • 八、HighLevelAPI 8.1、RestAPI介紹&項目導入 8.1.1、RestAPI介紹 ES官方提供了各種不同語言的客戶端,用來操作ES。這些客戶端的本質就是組裝DSL語句,通過http請求發送給ES 官方文檔地址 https://www.elastic.co/guide/en/elas ...
  • Java常用類 1.字元串相關類練習 1.1StringBuilder練習 package li.normalclass.stringbuilder; public class TestBuffer { public static void main(String[] args) { StringB ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • print函數中總是涉及到各式各樣的輸出,為了方便學習和查詢,今天在這裡特意做一個總結!註意:#後為輸出結果 1、“,”分隔 1 print("hello","world") #hello world(預設空格連接) 2、“+”連接 1 print("hello"+"world") #hellowo ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...