blog

ファンシー再生Spring Securityは、このような方法は、ユーザーを定義するために見ていない可能性がある!

時々、ソンはあなたとSpring Securityの冷たい使用法のいくつかを共有する、誇示するためではなく、ちょうどあなたがさまざまな角度からSpring Securityの理解を深めることができるこ...

Aug 7, 2020 · 7 min. read
シェア

ときどきSongがSpring Securityのコールドユーズを紹介することがありますが、見せびらかすためではなく、Spring Securityの理解を様々な角度から深めてもらうためです。純粋にソースコードや原理を説明してもよかったのですが、それではあまりにつまらないので、いくつかの小さなケースを通してソースコードを理解する手助けをしようと思います。これらのケースの目的は、Spring Securityのソースコードを理解する手助けをすること、それだけです!ですから、これらのユーザー定義メソッドが役に立たないなどと私に反論しないでください!

さて、今日はユーザーオブジェクトを派手に定義する裏技を紹介します。これらの事例を通して、ProviderManagerの動作メカニズムをより深く理解していただければ幸いです。

ジャグリング

次のコードから始めましょう:

@Configuration
public class SecurityConfig {
 @Bean
 UserDetailsService us() {
 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
 manager.createUser(User.withUsername("sang").password("{noop}123").roles("admin").build());
 return manager;
 }
 @Configuration
 @Order(1)
 static class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter {
 UserDetailsService us1() {
 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
 manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin", "aaa", "bbb").build());
 return manager;
 }
 @Override
 protected void configure(HttpSecurity http) throws Exception {
 http.antMatcher("/foo/**")
 .authorizeRequests()
 .anyRequest().hasRole("admin")
 .and()
 .formLogin()
 .loginProcessingUrl("/foo/login")
 .permitAll()
 .and()
 .userDetailsService(us1())
 .csrf().disable();
 }
 }
 @Configuration
 @Order(2)
 static class DefaultWebSecurityConfig2 extends WebSecurityConfigurerAdapter {
 UserDetailsService us2() {
 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
 manager.createUser(User.withUsername("雨").password("{noop}123").roles("user", "aaa", "bbb").build());
 return manager;
 }
 @Override
 protected void configure(HttpSecurity http) throws Exception {
 http.antMatcher("/bar/**")
 .authorizeRequests()
 .anyRequest().hasRole("user")
 .and()
 .formLogin()
 .loginProcessingUrl("/bar/login")
 .permitAll()
 .and()
 .csrf().disable()
 .userDetailsService(us2());
 }
 }
}

しかし、各フィルタ・チェーンでUserDetailsServiceインスタンスを提供し、configure(HttpSecurity http)メソッドでこのUserDetailsServiceインスタンスを構成していることに注目してください。各フィルタチェーンでUserDetailsServiceを設定することに加えて、UserDetailsService Beanも提供しているので、ここでは前後に合計3人のユーザーに相当するユーザーを設定しています。

まずは結論から:

  • ログインアドレスが/foo/loginの場合、sangとjavaboyの2人のユーザーでログインできます。
  • ログインアドレスが/bar/loginの場合は、笙と江南少し雨を介して2つのユーザーが正常にログインすることができます。

つまり、グローバルでパブリックなUserDetailsServiceは常に利用可能ですが、異なるフィルタチェーン用に設定されたUserDetailsServiceは、現在のフィルタチェーンでのみ利用可能です。

Pineでは便宜上メモリベースのUserDetailsServiceを使用していますが、もちろんデータベースベースのUserDetailsServiceに置き換えることもできます。

そこで以下では、なぜそうなるのかについて分析します。

ソースコード解析

グローバルAuthenticationManager

では、グローバルなAuthenticationManagerのコンフィギュレーションには何が書かれているのでしょうか?

public AuthenticationManager getAuthenticationManager() throws Exception {
	if (this.authenticationManagerInitialized) {
		return this.authenticationManager;
	}
	AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
	if (this.buildingAuthenticationManager.getAndSet(true)) {
		return new AuthenticationManagerDelegator(authBuilder);
	}
	for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
		authBuilder.apply(config);
	}
	authenticationManager = authBuilder.build();
	if (authenticationManager == null) {
		authenticationManager = getAuthenticationManagerBean();
	}
	this.authenticationManagerInitialized = true;
	return authenticationManager;
}

グローバル設定の1つのステップは、globalAuthConfigurersをトラバースして、 グローバルxxxConfigurerをトラバースして、それを設定することです。グローバルなxxxConfigurerは3つあります:

  • EnableGlobalAuthenticationAutowiredConfigurer
  • InitializeUserDetailsBeanManagerConfigurer
  • InitializeAuthenticationProviderBeanManagerConfigurer
@Order(InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER)
class InitializeUserDetailsBeanManagerConfigurer
		extends GlobalAuthenticationConfigurerAdapter {
	@Override
	public void init(AuthenticationManagerBuilder auth) throws Exception {
		auth.apply(new InitializeUserDetailsManagerConfigurer());
	}
	class InitializeUserDetailsManagerConfigurer
			extends GlobalAuthenticationConfigurerAdapter {
		@Override
		public void configure(AuthenticationManagerBuilder auth) throws Exception {
			if (auth.isConfigured()) {
				return;
			}
			UserDetailsService userDetailsService = getBeanOrNull(
					UserDetailsService.class);
			if (userDetailsService == null) {
				return;
			}
			PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
			UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
			DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
			provider.setUserDetailsService(userDetailsService);
			if (passwordEncoder != null) {
				provider.setPasswordEncoder(passwordEncoder);
			}
			if (passwordManager != null) {
				provider.setUserDetailsPasswordService(passwordManager);
			}
			provider.afterPropertiesSet();
			auth.authenticationProvider(provider);
		}
	}
}

ローカルAuthenticationManager

前の記事の研究を通じて、パートナーは、構築プロセスのすべてのHttpSecurityは、プロセスにローカルのAuthenticationManagerBuilderを渡すことを知って、このローカルのAuthenticationManagerBuilderは、共有オブジェクトに渡されると、将来的にあなたがそれを使用する必要があるときは、共有オブジェクトからそれを取り出し、コードの一部は次のとおりです:

public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor,
		AuthenticationManagerBuilder authenticationBuilder,
		Map<Class<?>, Object> sharedObjects) {
	super(objectPostProcessor);
	Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
	setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
	//省略
}
private AuthenticationManagerBuilder getAuthenticationRegistry() {
	return getSharedObject(AuthenticationManagerBuilder.class);
}
public HttpSecurity userDetailsService(UserDetailsService userDetailsService)
		throws Exception {
	getAuthenticationRegistry().userDetailsService(userDetailsService);
	return this;
}

つまり、ローカルの AuthenticationManager です。

この時点で、すべてのプロセスが明らかになりました。

そして、パインが下の図と合わせて説明します:

冒頭のケースを組み合わせると、例えば、ログインアドレスが/foo/loginで、ログインユーザがsang/123の場合、まずHttpSecurityのローカルProviderManagerで認証を行い、認証に失敗した場合、ローカルProviderManagerの親であるグローバル認証に入り、グローバルProviderManagerの対応するユーザがsangになります。認証に失敗した場合は、ローカルProviderManagerの親に認証に行く、つまり、グローバル認証であり、グローバルProviderManager内の対応するユーザが笙、認証が成功します。

少し遠回りかもしれませんが、前回の記事と合わせてじっくり味わっていただきたいプロセスです。

ジャグリング2

以下のように、SecurityConfig 定義を再度修正してください:

@Configuration
public class SecurityConfig {
 @Bean
 UserDetailsService us() {
 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
 manager.createUser(User.withUsername("sang").password("{noop}123").roles("admin").build());
 return manager;
 }
 @Configuration
 @Order(1)
 static class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter {
 UserDetailsService us1() {
 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
 manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin", "aaa", "bbb").build());
 return manager;
 }
 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
 auth.userDetailsService(us1());
 }
 @Override
 protected void configure(HttpSecurity http) throws Exception {
 http.antMatcher("/foo/**")
 .authorizeRequests()
 .anyRequest().hasRole("admin")
 .and()
 .formLogin()
 .loginProcessingUrl("/foo/login")
 .permitAll()
 .and()
 .csrf().disable();
 }
 }
 @Configuration
 @Order(2)
 static class DefaultWebSecurityConfig2 extends WebSecurityConfigurerAdapter {
 UserDetailsService us2() {
 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
 manager.createUser(User.withUsername("雨").password("{noop}123").roles("user", "aaa", "bbb").build());
 return manager;
 }
 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
 auth.userDetailsService(us2());
 }
 @Override
 protected void configure(HttpSecurity http) throws Exception {
 http.antMatcher("/bar/**")
 .authorizeRequests()
 .anyRequest().hasRole("user")
 .and()
 .formLogin()
 .loginProcessingUrl("/bar/login")
 .permitAll()
 .and()
 .csrf().disable();
 }
 }
}

configure(AuthenticationManagerBuilder auth) このコードで前回と変わった点は、メソッドを書き直したことです。前回の記事によると、このメソッドはグローバルなAuthenticationManangerの定義を無効にします。 つまり、/foo/loginも/bar/loginも、AuthenticationManangerの定義を使ってログインできなくなります。sang/123はログインできなくなります。

ユーザがログインすると、まず HttpSecurity フィルタ・チェイン内の ProviderManager に認証に行きますが、これは失敗します。

要約

このような構成の実際の開発では、ほとんど見ることはありませんが、上記の2つのケースでは、Spring Securityの認証プロセスの理解を深めることができ、パートナーは慎重に〜を味わうことができます!

パートナーが報われたと感じたら、松兄さんを励ますためにaを指差すことを忘れないでください。

Read next

カーネルレベルのスレッドのアドレス空間を探索する

最近、私はスレッドプロセスのこの部分を見て、私は記録するために、ここでこの問題を考えたときに学習しています。まず、2つのユーザーランドプロセスを切り替えるプロセスをおさらいしましょう。各ユーザー状態のプロセスは、カーネルレベルのスレッドに対応しています。オペレーティング・システムが実際のカーネルレベルのスレッドをスケジュールします。その後、カーネルレベルのスレッドは

Aug 7, 2020 · 1 min read