溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Spring Security用戶定義的方法是什么

發(fā)布時(shí)間:2021-12-07 13:50:16 來源:億速云 閱讀:116 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要介紹“Spring Security用戶定義的方法是什么”,在日常操作中,相信很多人在Spring Security用戶定義的方法是什么問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Spring Security用戶定義的方法是什么”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

1.絕活一

先來看如下一段代碼:

@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("江南一點(diǎn)雨").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());
        }
    }
}

大家注意,在每一個(gè)過濾器鏈中,我都提供了一個(gè) UserDetailsService 實(shí)例,然后在 configure(HttpSecurity http) 方法中,配置這個(gè) UserDetailsService 實(shí)例。除了每一個(gè)過濾器鏈中都配置一個(gè) UserDetailsService 之外,我還提供了一個(gè) UserDetailsService 的 Bean,所以這里前前后后相當(dāng)于一共有三個(gè)用戶,那么我們登錄時(shí)候,使用哪個(gè)用戶可以登錄成功呢?

先說結(jié)論:

  • 如果登錄地址是 /foo/login,那么通過 sang 和 javaboy 兩個(gè)用戶可以登錄成功。
  • 如果登錄地址是 /bar/login,那么通過 sang 和 江南一點(diǎn)雨 兩個(gè)用戶可以登錄成功。

也就是說,那個(gè)全局的,公共的 UserDetailsService 總是有效的,而針對(duì)不同過濾器鏈配置的 UserDetailsService 則只針對(duì)當(dāng)前過濾器鏈生效。

?  

這里為了方便,使用了基于內(nèi)存的 UserDetailsService,當(dāng)然你也可以替換為基于數(shù)據(jù)庫的 UserDetailsService。

那么接下來我們就來分析一下,為什么是這個(gè)樣子?

 

1.1 源碼分析

 
1.1.1 全局 AuthenticationManager

首先大家注意,雖然我定義了兩個(gè)過濾器鏈,但是在兩個(gè)過濾器鏈的定義中,我都沒有重寫 configure(AuthenticationManagerBuilder auth) 方法,結(jié)合上篇文章,沒有重寫這個(gè)方法,就意味著 AuthenticationConfiguration 中提供的全局 AuthenticationManager 是有效的,也就是說,系統(tǒng)默認(rèn)提供的 AuthenticationManager 將作為其他局部 AuthenticationManager 的 parent。

那么我們來看下全局的 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;
}
 

全局的配置中,有一步就是遍歷 globalAuthConfigurers,遍歷全局的 xxxConfigurer,并進(jìn)行配置。全局的 xxxConfigurer 一共有三個(gè),分別是:

  • EnableGlobalAuthenticationAutowiredConfigurer
  • InitializeUserDetailsBeanManagerConfigurer
  • InitializeAuthenticationProviderBeanManagerConfigurer

其中 InitializeUserDetailsBeanManagerConfigurer,看名字就是用來配置 UserDetailsService 的,我們來看下:

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

可以看到,InitializeUserDetailsBeanManagerConfigurer 中定義了內(nèi)部類,在其內(nèi)部類的 configure 方法中,通過 getBeanOrNull 去從容器中查找 UserDetailsService 實(shí)例,查找到之后,創(chuàng)建 DaoAuthenticationProvider,并最終配置給 auth 對(duì)象。

這里的 getBeanOrNull 方法從容器中查找到的,實(shí)際上就是 Spring 容器中的 Bean,也就是我們一開始配置了 sang 用戶的那個(gè) Bean,這個(gè) Bean 被交給了全局的 AuthenticationManager,也就是所有局部 AuthenticationManager 的 parent。

 
1.1.2 局部 AuthenticationManager

所有 HttpSecurity 在構(gòu)建的過程中,都會(huì)傳遞一個(gè)局部的 AuthenticationManagerBuilder 進(jìn)來,這個(gè)局部的 AuthenticationManagerBuilder 一旦傳進(jìn)來就存入了共享對(duì)象中,以后需要用的時(shí)候再從共享對(duì)象中取出來,部分代碼如下所示:

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

所以,我們?cè)?HttpSecurity 中配置 UserDetailsService,實(shí)際上是給這個(gè) AuthenticationManagerBuilder 配置的:

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

也就是局部 AuthenticationManager。

至此,整個(gè)流程就很清晰了。

再結(jié)合下面這張圖給大家解釋下:

Spring Security用戶定義的方法是什么  

每一個(gè)過濾器鏈都會(huì)綁定一個(gè)自己的 ProviderManager(即 AuthenticationManager 的實(shí)現(xiàn)),而每一個(gè) ProviderManager 中都通過 DaoAuthenticationProvider 持有一個(gè) UserDetailsService 對(duì)象,你可以簡(jiǎn)單理解為一個(gè) ProviderManager 管理了一個(gè) UserDetailsService,當(dāng)我們開始認(rèn)證的時(shí)候,首先由過濾器鏈所持有的局部 ProviderManager 去認(rèn)證,要是認(rèn)證失敗了,則調(diào)用 ProviderManager 的 parent 再去認(rèn)證,此時(shí)就會(huì)用到全局 AuthenticationManager 所持有的 UserDetailsService 對(duì)象了。

結(jié)合一開始的案例,例如你的登錄地址是 /foo/login,如果你的登錄用戶是 sang/123,那么先去 HttpSecurity 的局部 ProviderManager 中去驗(yàn)證,結(jié)果驗(yàn)證失?。ň植康?ProviderManager 中對(duì)應(yīng)的用戶是 javaboy),此時(shí)就會(huì)進(jìn)入局部 ProviderManager 的 parent 中去認(rèn)證,也就是全局認(rèn)證,全局的 ProviderManager 中對(duì)應(yīng)的用戶就是 sang 了,此時(shí)就認(rèn)證成功。


 

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("江南一點(diǎn)雨").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) 方法,根據(jù)上篇文章的介紹,重寫了該方法之后,全局的 AuthenticationMananger 定義就失效了,也就意味著 sang 這個(gè)用戶定義失效了,換言之,無論是 /foo/login 還是 /bar/login,使用 sang/123 現(xiàn)在都無法登錄了。

在每一個(gè) HttpSecurity 過濾器鏈中,我都重寫了 configure(AuthenticationManagerBuilder auth) 方法,并且重新配置了 UserDetailsService,這個(gè)重寫,相當(dāng)于我在定義 parent 級(jí)別的 ProviderManager。而每一個(gè) HttpSecurity 過濾器鏈則不再包含 UserDetailsService。

當(dāng)用戶登錄時(shí),先去找到 HttpSecurity 過濾器鏈中的 ProviderManager 去認(rèn)證,結(jié)果認(rèn)證失敗,然后再找到 ProviderManager 的 parent 去認(rèn)證,就成功了。

到此,關(guān)于“Spring Security用戶定義的方法是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI