“致"高級"工程師(BUG工程師)
一顆折騰的心💗
原創不易,點個贊💗,支持支持
在認證過程和訪問授權前必須了解spring Security如何知道我們要求所有用戶都經過身份驗證? Spring Security如何知道我們想要支持基於表單的身份驗證?因此必須了解WebSecurityConfigurerAdapter配置類如何工作的。而且也必須了解清楚filter的順序,才能更好了解其調用工作流程。
1. WebSecurityConfigurerAdapter
在使用WebSecurityConfigurerAdapter前,先了解Spring security config。 Spring security config具有三個模塊,一共有3個builder,認證相關的AuthenticationManagerBuilder和web相關的WebSecurity、HttpSecurity。
-
AuthenticationManagerBuilder:用來配置全局的認證相關的信息,其實就是AuthenticationProvider和UserDetailsService,前者是認證服務提供商,后者是用戶詳情查詢服務;
-
WebSecurity: 全局請求忽略規則配置(比如說靜態文件,比如說注冊頁面)、全局HttpFirewall配置、是否debug配置、全局SecurityFilterChain配置、privilegeEvaluator、expressionHandler、securityInterceptor;
-
HttpSecurity:具體的權限控制規則配置。一個這個配置相當於xml配置中的一個標簽。各種具體的認證機制的相關配置,OpenIDLoginConfigurer、AnonymousConfigurer、FormLoginConfigurer、HttpBasicConfigurer等。
WebSecurityConfigurerAdapter提供了簡潔方式來創建WebSecurityConfigurer,其作為基類,可通過實現該類自定義配置類,主要重寫這三個方法:
protected void configure(AuthenticationManagerBuilder auth) throws Exception {} public void configure(WebSecurity web) throws Exception {} protected void configure(HttpSecurity httpSecurity) throws Exception {}
而且其自動從SpringFactoriesLoader查找AbstractHttpConfigurer讓我們去擴展,想要實現必須創建一個AbstractHttpConfigurer的擴展類,並在classpath路徑下創建一個文件META-INF/spring.factories。例如: org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer
其源碼分析:
//1.init初始化:獲取HttpSecurity和配置FilterSecurityInterceptor攔截器到WebSecurity public void init(final WebSecurity web) throws Exception { //獲取HttpSecurity final HttpSecurity http = getHttp(); //配置FilterSecurityInterceptor攔截器到WebSecurity web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() { public void run() { FilterSecurityInterceptor securityInterceptor = http .getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); } }); } ...... //2.獲取HttpSecurity的過程 protected final HttpSecurity getHttp() throws Exception { if (http != null) { return http; } DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor .postProcess(new DefaultAuthenticationEventPublisher()); localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher); AuthenticationManager authenticationManager = authenticationManager(); authenticationBuilder.parentAuthenticationManager(authenticationManager); Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects(); http = new HttpSecurity(objectPostProcessor, authenticationBuilder, sharedObjects); if (!disableDefaults) { // 默認的HttpSecurity的配置 http //添加 CSRF 支持,使用WebSecurityConfigurerAdapter時,默認啟用,禁用csrf().disable() .csrf().and() //添加WebAsyncManagerIntegrationFilter .addFilter(new WebAsyncManagerIntegrationFilter()) //允許配置異常處理 .exceptionHandling().and() //將安全標頭添加到響應 .headers().and() //允許配置會話管理 .sessionManagement().and() //HttpServletRequest之間的SecurityContextHolder創建securityContext管理 .securityContext().and() //允許配置請求緩存 .requestCache().and() //允許配置匿名用戶 .anonymous().and() //HttpServletRequestd的方法和屬性注冊在SecurityContext中 .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; } ... //3.可重寫方法實現自定義的 HttpSecurity 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(); } ....
從源碼init初始化模塊中的“獲取HttpSecurity”和“配置FilterSecurityInterceptor攔截器到WebSecurity”中可以看出,想要spring Security如何知道我們要求所有用戶都經過身份驗證? Spring Security如何知道我們想要支持基於表單的身份驗證?只要重寫protected void configure(HttpSecurity http) throws Exception方法即可。因此我們需要理解HttpSecurity的方法的作用,如何進行配置。下一節來討論HttpSecurity。
2. HttpSecurity
HttpSecurity基於Web的安全性允許為特定的http請求進行配置。其有很多方法,列舉一些常用的如下表:
方法 | 說明 | 使用案例 |
---|---|---|
csrf() | 添加 CSRF 支持,使用WebSecurityConfigurerAdapter時,默認啟用 | 禁用:csrf().disable() |
openidLogin() | 用於基於 OpenId 的驗證 | openidLogin().permitAll(); |
authorizeRequests() | 開啟使用HttpServletRequest請求的訪問限制 | authorizeRequests().anyRequest().authenticated() |
formLogin() | 開啟表單的身份驗證,如果未指定FormLoginConfigurer#loginPage(String),則將生成默認登錄頁面 | formLogin().loginPage("/authentication/login").failureUrl("/authentication/login?failed") |
oauth2Login() | 開啟OAuth 2.0或OpenID Connect 1.0身份驗證 | authorizeRequests()..anyRequest().authenticated()..and().oauth2Login() |
rememberMe() | 開啟配置“記住我”的驗證 | authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin().permitAll().and().rememberMe() |
addFilter() | 添加自定義的filter | addFilter(new CustomFilter()) |
addFilterAt() | 在指定filter相同位置上添加自定義filter | addFilterAt(new CustomFilter(), UsernamePasswordAuthenticationFilter.class) |
addFilterAfter() | 在指定filter位置后添加自定義filter | addFilterAfter(new CustomFilter(), UsernamePasswordAuthenticationFilter.class) |
requestMatchers() | 開啟配置HttpSecurity,僅當RequestMatcher相匹配時開啟 | requestMatchers().antMatchers("/api/**") |
antMatchers() | 其可以與authorizeRequests()、RequestMatcher匹配,如:requestMatchers().antMatchers("/api/**") | |
logout() | 添加退出登錄支持。當使用WebSecurityConfigurerAdapter時,這將自動應用。默認情況是,訪問URL”/ logout”,使HTTP Session無效來清除用戶,清除已配置的任何#rememberMe()身份驗證,清除SecurityContextHolder,然后重定向到”/login?success” | logout().deleteCookies("remove").invalidateHttpSession(false).logoutUrl("/custom-logout").logoutSuccessUrl("/logout-success"); |
HttpSecurity還有很多方法供我們使用,去配置HttpSecurity。由於太多這邊就不一一說明,有興趣可去研究。
3. WebSecurityConfigurerAdapter使用
WebSecurityConfigurerAdapter示例:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyFilterSecurityInterceptor myFilterSecurityInterceptor; protected void configure(HttpSecurity http) throws Exception { http //request 設置 .authorizeRequests() //http.authorizeRequests() 方法中的自定義匹配 .antMatchers("/resources/**", "/signup", "/about").permitAll() // 指定所有用戶進行訪問指定的url .antMatchers("/admin/**").hasRole("ADMIN") //指定具有特定權限的用戶才能訪問特定目錄,hasRole()方法指定用戶權限,且不需前綴 “ROLE_“ .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")// .anyRequest().authenticated() //任何請求沒匹配的都需要進行驗證 .and() //login設置 自定義登錄頁面且允許所有用戶登錄 .formLogin() .loginPage("/login") //The updated configuration specifies the location of the log in page 指定自定義登錄頁面 .permitAll(); // 允許所有用戶訪問登錄頁面. The formLogin().permitAll() 方法 .and .logout() //logouts 設置 .logoutUrl("/my/logout") // 指定注銷路徑 .logoutSuccessUrl("/my/index") //指定成功注銷后跳轉到指定的頁面 .logoutSuccessHandler(logoutSuccessHandler) //指定成功注銷后處理類 如果使用了logoutSuccessHandler()的話, logoutSuccessUrl()就會失效 .invalidateHttpSession(true) // httpSession是否有效時間,如果使用了 SecurityContextLogoutHandler,其將被覆蓋 .addLogoutHandler(logoutHandler) //在最后增加默認的注銷處理類LogoutHandler .deleteCookies(cookieNamesToClear);//指定注銷成功后remove cookies //增加在FilterSecurityInterceptor前添加自定義的myFilterSecurityInterceptor http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class); }
NOTE:此示例只供參考
4. filter順序
Spring Security filter順序:
Filter Class | 說明 |
---|---|
ChannelProcessingFilter | 訪問協議控制過濾器,可能會將我們重新定向到另外一種協議,從http轉換成https |
SecurityContextPersistenceFilter | 創建SecurityContext安全上下文信息和request結束時清空SecurityContextHolder |
ConcurrentSessionFilter | 並發訪問控制過濾器,主要功能:SessionRegistry中獲取SessionInformation來判斷session是否過期,從而實現並發訪問控制。 |
HeaderWriterFilter | 給http response添加一些Header |
CsrfFilter | 跨域過濾器,跨站請求偽造保護Filter |
LogoutFilter | 處理退出登錄的Filter |
X509AuthenticationFilter | 添加X509預授權處理機制支持 |
CasAuthenticationFilter | 認證filter,經過這些過濾器后SecurityContextHolder中將包含一個完全組裝好的Authentication對象,從而使后續鑒權能正常執行 |
UsernamePasswordAuthenticationFilter | 認證的filter,經過這些過濾器后SecurityContextHolder中將包含一個完全組裝好的Authentication對象,從而使后續鑒權能正常執行。表單認證是最常用的一個認證方式。 |
BasicAuthenticationFilter | 認證filter,經過這些過濾器后SecurityContextHolder中將包含一個完全組裝好的Authentication對象,從而使后續鑒權能正常執行 |
SecurityContextHolderAwareRequestFilter | 此過濾器對ServletRequest進行了一次包裝,使得request具有更加豐富的API |
JaasApiIntegrationFilter | (JAAS)認證方式filter |
RememberMeAuthenticationFilter | 記憶認證處理過濾器,即是如果前面認證過濾器沒有對當前的請求進行處理,啟用了RememberMe功能,會從cookie中解析出用戶,並進行認證處理,之后在SecurityContextHolder中存入一個Authentication對象。 |
AnonymousAuthenticationFilter | 匿名認證處理過濾器,當SecurityContextHolder中認證信息為空,則會創建一個匿名用戶存入到SecurityContextHolder中 |
SessionManagementFilter | 會話管理Filter,持久化用戶登錄信息,可以保存到session中,也可以保存到cookie或者redis中 |
ExceptionTranslationFilter | 異常處理過濾器,主要攔截后續過濾器(FilterSecurityInterceptor)操作中拋出的異常。 |
FilterSecurityInterceptor | 安全攔截過濾器類,獲取當前請求url對應的ConfigAttribute,並調用accessDecisionManager進行訪問授權決策。 |
spring security的默認filter鏈:
SecurityContextPersistenceFilter
->HeaderWriterFilter
->LogoutFilter
->UsernamePasswordAuthenticationFilter
->RequestCacheAwareFilter
->SecurityContextHolderAwareRequestFilter
->SessionManagementFilter
->ExceptionTranslationFilter
->FilterSecurityInterceptor
在上節我們已分析了核心的filter源碼以及功能。可回看上節源碼分析更加深入的了解各個filter工作原理。
總結:
在認證和訪問授權過程前,首先必須進行WebSecurityConfigurer符合自身應用的security Configurer,也要清楚filter鏈的先后順序,才能更好理解spring security的工作原理以及在項目中出現的問題定位。了解完准備工作,接下來將展開對認證和訪問授權模塊的工作流程研究以及項目示例分析。最后如有錯誤可評論告知。
各位看官還可以嗎?喜歡的話,動動手指點個贊💗,點個關注唄!!謝謝支持!
也歡迎關注公眾號【Ccww筆記】,原創技術文章第一時間推出