blog

WebSecurityConfigurerAdapterソースコードの章を深く理解する

は、Spring Securityのソースコードをジャックし続け、今日は非常に重要な. カスタマイズは、実装から継承されますが、ために......

Jun 3, 2020 · 9 min. read
Share this

WebSecurityConfigurerAdapter のカスタマイズは WebSecurityConfigurerAdapter を継承して実現しますが、 WebSecurityConfigurerAdapter の内部的な仕組みや設定原理については、パートナーの多くはあまりよく知らないかもしれません。

WebSecurityConfigurerAdapter の継承図を見てみましょう:

このレベルの継承には、2つの非常に重要なクラスがあります:

  • SecurityBuilder
  • SecurityConfigurer

ですから、ここではこれら2つのクラスの紹介とその役割については触れません。WebSecurityConfigurer から始めましょう。

WebSecurityConfigurer

WebSecurityConfigurer は実際には空のインターフェイスですが、以下のようにいくつかの汎用的な制約を持っています:

public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
		SecurityConfigurer<Filter, T> {
}

WebSecurityConfigurer の目的が何であるかということです!

  1. SecurityConfigurerの2つのジェネリックタイプのうちの最初のタイプは、SecurityBuilderが最終的に構築するオブジェクトでもあります。

WebSecurity

WebSecurityの定義を見てください:

public final class WebSecurity extends
		AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
		SecurityBuilder<Filter>, ApplicationContextAware {
}

WebSecurityでは、これらのインターフェイスや継承クラスを、前回のソースコード解析で歌い、ご紹介しましたが、忘れてしまったパートナーもいらっしゃるかもしれませんので、また一緒に復習しましょう。

AbstractConfiguredSecurityBuilder です。

まず、AbstructConfiguredSecurityBuilder では、ビルドプロセス全体を、ビルドプロセスのライフサイクルの 5 つのフェーズとも解釈できる、以下のような 5 つの状態に分割する列挙クラスを定義しています:

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

AbstractConfiguredSecurityBuilder にはたくさんのメソッドがありますが、Pine はここで 2 つの重要なメソッドをリストアップし、皆さんと一緒に分析します:

private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
	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<>(1);
		}
		configs.add(configurer);
		this.configurers.put(clazz, configs);
		if (buildState.isInitializing()) {
			this.configurersAddedInInitializing.add(configurer);
		}
	}
}
private Collection<SecurityConfigurer<O, B>> getConfigurers() {
	List<SecurityConfigurer<O, B>> result = new ArrayList<>();
	for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
		result.addAll(configs);
	}
	return result;
}

最初のメソッドは add メソッドで、これはすべてのコンフィギュレーションクラスを収集することに相当します。configurers 自体は LinkedHashMap で、キーはコンフィギュレーションクラスのクラス、値はコレクションで、xxxConfigure コンフィギュレーションクラスが含まれます。これらの設定クラスを一元的に設定する必要がある場合、getConfigurers メソッドを通して設定クラスを取得します。取得のプロセスは、LinkedHashMap から値を取り出し、コレクションに入れて返すというものです。

もうひとつのメソッドは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;
	}
}
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);
	}
}
private void configure() throws Exception {
	Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
	for (SecurityConfigurer<O, B> configurer : configurers) {
		configurer.configure((B) this);
	}
}

doBuildメソッドは、状態を更新しながら初期化を行います。

beforeInit は実装のない予約メソッドです。

initメソッドはすべてのxxxConfigureを見つけ、それらのinitメソッドを1つずつ呼び出して初期化します。

beforeConfigure は実装のない予約メソッドです。

セキュリティビルダー

ウェブセキュリティ

WebSecurityのコアロジックはperformBuildビルドメソッドに焦点を当てています:

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

このスレッドを把握した後、メソッドの実装を見るのは簡単です。

  1. まず第一に、フィルタチェーンの統計情報の総数、2つの側面を含むエントリの総数、1つは無視される要求のWebSecurityの設定を通じて、無視される要求であるignoredRequestsは、松兄が導入され、参照してください:Springのセキュリティ2つのリソースリリースポリシーは、間違って使用しないでください!もう一つは、HttpSecurityを介して設定されたフィルタのチェーンであるsecurityFilterChainBuildersであり、それらのいくつかあります。
  2. securityFilterChains コレクションを作成し、前述の 2 種類のフィルタ・チェーンを繰り返し 処理して、フィルタ・チェーンを securityFilterChains コレクションに配置します。
  3. 最後に返されるのはFilterChainProxyのインスタンスです。

この分析から、WebSecurityとHttpSecurityの違いがわかります:

  1. HttpSecurityの目的は、フィルタの連鎖を構築することです。 HttpSecurityオブジェクトはフィルタの連鎖を構築し、フィルタの連鎖にはN個のフィルタがあり、HttpSecurityが行っていることは、実際にこれらのN個のフィルタを設定することです。

これはWebSecurityの主な役割で、中心的なメソッドはperformBuildです。他のメソッドは比較的単純なので、宋は一つ一つ説明しません。

WebSecurityConfigurer

メソッドのWebSecurityConfigurerAdapterはもっとですが、前の分析によると、メソッドの概要は2つです、1つはinitは、configure(WebSecurityのWeb)があり、他のメソッドは、これらの2つのメソッドのサービスのためのものです。これらの2つのメソッドを見てみましょう:

initメソッドから始めましょう:

public void init(final WebSecurity web) throws Exception {
	final HttpSecurity http = getHttp();
	web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
		FilterSecurityInterceptor securityInterceptor = http
				.getSharedObject(FilterSecurityInterceptor.class);
		web.securityInterceptor(securityInterceptor);
	});
}
protected final HttpSecurity getHttp() throws Exception {
	if (http != null) {
		return http;
	}
	AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
	localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
	AuthenticationManager authenticationManager = authenticationManager();
	authenticationBuilder.parentAuthenticationManager(authenticationManager);
	Map<Class<?>, Object> sharedObjects = createSharedObjects();
	http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
			sharedObjects);
	if (!disableDefaults) {
		// @formatter:off
		http
			.csrf().and()
			.addFilter(new WebAsyncManagerIntegrationFilter())
			.exceptionHandling().and()
			.headers().and()
			.sessionManagement().and()
			.securityContext().and()
			.requestCache().and()
			.anonymous().and()
			.servletApi().and()
			.apply(new DefaultLoginPageConfigurer<>()).and()
			.logout();
		// @formatter:on
		ClassLoader classLoader = this.context.getClassLoader();
		List<AbstractHttpConfigurer> defaultHttpConfigurers =
				SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
		for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
			http.apply(configurer);
		}
	}
	configure(http);
	return http;
}
protected void configure(HttpSecurity http) throws Exception {
	logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
	http
		.authorizeRequests()
			.anyRequest().authenticated()
			.and()
		.formLogin().and()
		.httpBasic();
}

まず getHttp メソッドを呼び出して HttpSecurity を初期化し、実際にはデフォルトのフィルタの束を設定し、最後に configure(http) メソッドを呼び出していくつかのインターセプタを設定しますが、実際には configure(http) メソッドは書き換えられることが多いです。実際の開発では、configure(http) メソッドはしばしば書き直されますし、この連載の以前の記事でも、configure(http) メソッドはほとんど書き直されています。HttpSecurity の設定が完了したら、HttpSecurity を WebSecurity の中に入れてsecurityFilterChainBuilders コレクションに保存されます。

configure(WebSecurity web) メソッドは実際には空のメソッドで、実際の開発ではオーバーライドされるかもしれません:

public void configure(WebSecurity web) throws Exception {
}

WebSecurity

これは、WebSecurityConfigurerAdapterですが、全体的に難しくはありませんが、一緒にいくつかの以前のソースコード解析の記事を松に、理解がより深くなります。

ポータル:

  1. FilterChainProxyソースコードの章の深い理解
  2. SecurityConfigurerソースコードの章の深い理解
  3. HttpSecurityソースコードの章をより深く理解します。
  4. AuthenticationManagerBuilderソースコード章の深い理解

さて、もしあなたが得をしたなら、松兄を励ますために見た目をクリックすることを忘れないでください。

Read next

No articles found.

No articles found.