spring security 之自定義表單登錄源碼跟蹤


​ 上一節我們跟蹤了security的默認登錄頁的源碼,可以參考這里:https://www.cnblogs.com/process-h/p/15522267.html 這節我們來看看如何自定義單表認證頁及源碼跟蹤。

​ 為了實現自定義表單及登錄頁,我們需要編寫自己的WebSecurityConfig類,繼承了WebSecurityConfigurerAdapter對象,通過重寫configure方法,定義自己的登錄頁路徑及失敗跳轉的路徑。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests
                                .antMatchers("/css/**", "/index").permitAll()
                                .antMatchers("/user/**").hasRole("USER")
                )
                .formLogin(formLogin ->
                        formLogin
                                .loginPage("/login")
                                .failureUrl("/login-error")
                );
    }
    // @formatter:on

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(userDetails);
    }
}

我們通過引入Thymeleaf模板來實現跳轉

@Controller
public class MainController {

   @RequestMapping("/")
   public String root() {
      return "redirect:/index";
   }

   @RequestMapping("/index")
   public String index() {
      return "index";
   }

   @RequestMapping("/user/index")
   public String userIndex() {
      return "user/index";
   }

   @RequestMapping("/login")
   public String login() {
      return "login";
   }

   @RequestMapping("/login-error")
   public String loginError(Model model) {
      model.addAttribute("loginError", true);
      return "login";
   }

}

上一節我們提到了WebSecurityConfig類,它會有一個init方法

@Override
public void init(WebSecurity web) throws Exception {
   HttpSecurity http = getHttp();
   web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
      FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class);
      web.securityInterceptor(securityInterceptor);
   });
}

這里提到了HttpSecurity對象,顧名思義,它的作用就是保證Http請求的安全,那么它是如何保證http請求的安全的呢?我們來看看getHttp()方法

protected final HttpSecurity getHttp() throws Exception {
   if (this.http != null) {
      return this.http;
   }
    // 初始化認證事件發布者,也就是定義了一些異常跟異常事件類之前的映射關系
   AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
   this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
    // 初始化認證管理者
   AuthenticationManager authenticationManager = authenticationManager();
   this.authenticationBuilder.parentAuthenticationManager(authenticationManager);
   Map<Class<?>, Object> sharedObjects = createSharedObjects();
   this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects);
   if (!this.disableDefaults) {
       // 默認情況下會去加載配置
      applyDefaultConfiguration(this.http);
      ClassLoader classLoader = this.context.getClassLoader();
      List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader
            .loadFactories(AbstractHttpConfigurer.class, classLoader);
      for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
         this.http.apply(configurer);
      }
   }
   configure(this.http);
   return this.http;
}

// 默認認證事件發布者
public DefaultAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
		this.applicationEventPublisher = applicationEventPublisher;
		addMapping(BadCredentialsException.class.getName(), AuthenticationFailureBadCredentialsEvent.class);
		addMapping(UsernameNotFoundException.class.getName(), AuthenticationFailureBadCredentialsEvent.class);
		addMapping(AccountExpiredException.class.getName(), AuthenticationFailureExpiredEvent.class);
		addMapping(ProviderNotFoundException.class.getName(), AuthenticationFailureProviderNotFoundEvent.class);
		addMapping(DisabledException.class.getName(), AuthenticationFailureDisabledEvent.class);
		addMapping(LockedException.class.getName(), AuthenticationFailureLockedEvent.class);
		addMapping(AuthenticationServiceException.class.getName(), AuthenticationFailureServiceExceptionEvent.class);
		addMapping(CredentialsExpiredException.class.getName(), AuthenticationFailureCredentialsExpiredEvent.class);
		addMapping("org.springframework.security.authentication.cas.ProxyUntrustedException",
				AuthenticationFailureProxyUntrustedEvent.class);
		addMapping("org.springframework.security.oauth2.server.resource.InvalidBearerTokenException",
				AuthenticationFailureBadCredentialsEvent.class);
	}

我們來看看applyDefaultConfiguration這個方法,在上一節有講到,這里是給httpSecurity對象配置一些默認的配置,比如默認會開啟csrf跨站請求偽造防護,添加WebAsyncManagerIntegrationFilter過濾器,添加默認的登錄頁配置DefaultLoginPageConfigurer等。

private void applyDefaultConfiguration(HttpSecurity http) throws Exception {
   http.csrf();
   http.addFilter(new WebAsyncManagerIntegrationFilter());
   http.exceptionHandling();
   http.headers();
   http.sessionManagement();
   http.securityContext();
   http.requestCache();
   http.anonymous();
   http.servletApi();
   http.apply(new DefaultLoginPageConfigurer<>());
   http.logout();
}

回到調用applyDefaultConfiguration()的主方法這里,執行完if (!this.disableDefaults) {}分支之后,會調用自身的configure(this.http);方法,也就是我們自定義的WebSecurityConfig類中重寫的方法,會去執行我們的表單登錄配置策略。

.formLogin(formLogin ->
        formLogin
                .loginPage("/login")
                .failureUrl("/login-error")
);
@Override
public FormLoginConfigurer<H> loginPage(String loginPage) {
    return super.loginPage(loginPage);
}
protected T loginPage(String loginPage) {
    setLoginPage(loginPage);
    updateAuthenticationDefaults();
    this.customLoginPage = true;
    return getSelf();
}

點擊.loginPage("/login")方法,再點擊super.loginPage(loginPage); 可以看到登錄頁已經被重寫了,自定義登錄頁標志也被寫成了true。

​ 自定義表單登錄頁及源碼跟蹤就到這里,過程中還發現了跟security最為密切的filter順序定義,在該FilterOrderRegistration類的構造方法中,定義了security中可能會用到的所有filter的順序,有興趣的讀者自行閱讀下。登錄相關的源碼跟的線條比較粗,接下來該看看認證跟授權的部分了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM