??在認(rèn)證過(guò)程和訪問(wèn)授權(quán)前必須了解spring Security如何知道我們要求所有用戶都經(jīng)過(guò)身份驗(yàn)證? Spring Security如何知道我們想要支持基于表單的身份驗(yàn)證?因此必須了解WebSecurityConfigurerAdapter配置類如何工作的。而且也必須了解清楚filter的順序,才能更好了解其調(diào)用工作流程。
1. WebSecurityConfigurerAdapter
??在使用WebSecurityConfigurerAdapter前,先了解Spring security config。
??Spring security config具有三個(gè)模塊,一共有3個(gè)builder,認(rèn)證相關(guān)的AuthenticationManagerBuilder和web相關(guān)的WebSecurity、HttpSecurity。
AuthenticationManagerBuilder:用來(lái)配置全局的認(rèn)證相關(guān)的信息,其實(shí)就是AuthenticationProvider和UserDetailsService,前者是認(rèn)證服務(wù)提供商,后者是用戶詳情查詢服務(wù);
WebSecurity: 全局請(qǐng)求忽略規(guī)則配置(比如說(shuō)靜態(tài)文件,比如說(shuō)注冊(cè)頁(yè)面)、全局HttpFirewall配置、是否debug配置、全局SecurityFilterChain配置、privilegeEvaluator、expressionHandler、securityInterceptor;
-
HttpSecurity:具體的權(quán)限控制規(guī)則配置。一個(gè)這個(gè)配置相當(dāng)于xml配置中的一個(gè)標(biāo)簽。各種具體的認(rèn)證機(jī)制的相關(guān)配置,OpenIDLoginConfigurer、AnonymousConfigurer、FormLoginConfigurer、HttpBasicConfigurer等。
??WebSecurityConfigurerAdapter提供了簡(jiǎn)潔方式來(lái)創(chuàng)建WebSecurityConfigurer,其作為基類,可通過(guò)實(shí)現(xiàn)該類自定義配置類,主要重寫這三個(gè)方法:
protected void configure(AuthenticationManagerBuilder auth) throws Exception {} public void configure(WebSecurity web) throws Exception {} protected void configure(HttpSecurity httpSecurity) throws Exception {}??而且其自動(dòng)從SpringFactoriesLoader查找AbstractHttpConfigurer讓我們?nèi)U(kuò)展,想要實(shí)現(xiàn)必須創(chuàng)建一個(gè)AbstractHttpConfigurer的擴(kuò)展類,并在classpath路徑下創(chuàng)建一個(gè)文件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的過(guò)程 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) { // 默認(rèn)的HttpSecurity的配置 http //添加 CSRF 支持,使用WebSecurityConfigurerAdapter時(shí),默認(rèn)啟用,禁用csrf().disable() .csrf().and() //添加WebAsyncManagerIntegrationFilter .addFilter(new WebAsyncManagerIntegrationFilter()) //允許配置異常處理 .exceptionHandling().and() //將安全標(biāo)頭添加到響應(yīng) .headers().and() //允許配置會(huì)話管理 .sessionManagement().and() //HttpServletRequest之間的SecurityContextHolder創(chuàng)建securityContext管理 .securityContext().and() //允許配置請(qǐng)求緩存 .requestCache().and() //允許配置匿名用戶 .anonymous().and() //HttpServletRequestd的方法和屬性注冊(cè)在SecurityContext中 .servletApi().and() //使用默認(rèn)登錄頁(yè)面 .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.可重寫方法實(shí)現(xiàn)自定義的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如何知道我們要求所有用戶都經(jīng)過(guò)身份驗(yàn)證? Spring Security如何知道我們想要支持基于表單的身份驗(yàn)證?只要重寫protected void configure(HttpSecurity http) throws Exception方法即可。因此我們需要理解HttpSecurity的方法的作用,如何進(jìn)行配置。下一節(jié)來(lái)討論HttpSecurity。2. HttpSecurity
??HttpSecurity基于Web的安全性允許為特定的http請(qǐng)求進(jìn)行配置。其有很多方法,列舉一些常用的如下表:
| 方法 | 說(shuō)明 | 使用案例 |
|---|---|---|
| csrf() | 添加 CSRF 支持,使用WebSecurityConfigurerAdapter時(shí),默認(rèn)啟用 | 禁用:csrf().disable() |
| openidLogin() | 用于基于 OpenId 的驗(yàn)證 | openidLogin().permitAll(); |
| authorizeRequests() | 開(kāi)啟使用HttpServletRequest請(qǐng)求的訪問(wèn)限制 | authorizeRequests().anyRequest().authenticated() |
| formLogin() | 開(kāi)啟表單的身份驗(yàn)證,如果未指定FormLoginConfigurer#loginPage(String),則將生成默認(rèn)登錄頁(yè)面 | formLogin().loginPage("/authentication/login").failureUrl("/authentication/login?failed") |
| oauth2Login() | 開(kāi)啟OAuth 2.0或OpenID Connect 1.0身份驗(yàn)證 | authorizeRequests()..anyRequest().authenticated()..and().oauth2Login() |
| rememberMe() | 開(kāi)啟配置“記住我”的驗(yàn)證 | 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() | 開(kāi)啟配置HttpSecurity,僅當(dāng)RequestMatcher相匹配時(shí)開(kāi)啟 | requestMatchers().antMatchers("/api/**") |
| antMatchers() | 其可以與authorizeRequests()、RequestMatcher匹配,如:requestMatchers().antMatchers("/api/**") | |
| logout() | 添加退出登錄支持。當(dāng)使用WebSecurityConfigurerAdapter時(shí),這將自動(dòng)應(yīng)用。默認(rèn)情況是,訪問(wèn)URL”/ logout”,使HTTP Session無(wú)效來(lái)清除用戶,清除已配置的任何#rememberMe()身份驗(yàn)證,清除SecurityContextHolder,然后重定向到”/login?success” | logout().deleteCookies("remove").invalidateHttpSession(false).logoutUrl("/custom-logout").logoutSuccessUrl("/logout-success"); |
HttpSecurity還有很多方法供我們使用,去配置HttpSecurity。由于太多這邊就不一一說(shuō)明,有興趣可去研究。
3. WebSecurityConfigurerAdapter使用
WebSecurityConfigurerAdapter示例:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
protected void configure(HttpSecurity http) throws Exception {
http
//request 設(shè)置
.authorizeRequests() //http.authorizeRequests() 方法中的自定義匹配
.antMatchers("/resources/**", "/signup", "/about").permitAll() // 指定所有用戶進(jìn)行訪問(wèn)指定的url
.antMatchers("/admin/**").hasRole("ADMIN") //指定具有特定權(quán)限的用戶才能訪問(wèn)特定目錄,hasRole()方法指定用戶權(quán)限,且不需前綴 “ROLE_“
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")//
.anyRequest().authenticated() //任何請(qǐng)求沒(méi)匹配的都需要進(jìn)行驗(yàn)證
.and() //login設(shè)置 自定義登錄頁(yè)面且允許所有用戶登錄
.formLogin()
.loginPage("/login") //The updated configuration specifies the location of the log in page 指定自定義登錄頁(yè)面
.permitAll(); // 允許所有用戶訪問(wèn)登錄頁(yè)面. The formLogin().permitAll() 方法
.and
.logout() //logouts 設(shè)置
.logoutUrl("/my/logout") // 指定注銷路徑
.logoutSuccessUrl("/my/index") //指定成功注銷后跳轉(zhuǎn)到指定的頁(yè)面
.logoutSuccessHandler(logoutSuccessHandler) //指定成功注銷后處理類 如果使用了logoutSuccessHandler()的話, logoutSuccessUrl()就會(huì)失效
.invalidateHttpSession(true) // httpSession是否有效時(shí)間,如果使用了 SecurityContextLogoutHandler,其將被覆蓋
.addLogoutHandler(logoutHandler) //在最后增加默認(rèn)的注銷處理類LogoutHandler
.deleteCookies(cookieNamesToClear);//指定注銷成功后remove cookies
//增加在FilterSecurityInterceptor前添加自定義的myFilterSecurityInterceptor
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
}
NOTE:此示例只供參考
4. filter順序
Spring Security filter順序:
| Filter Class | 說(shuō)明 |
|---|---|
| ChannelProcessingFilter | 訪問(wèn)協(xié)議控制過(guò)濾器,可能會(huì)將我們重新定向到另外一種協(xié)議,從http轉(zhuǎn)換成https |
| SecurityContextPersistenceFilter | 創(chuàng)建SecurityContext安全上下文信息和request結(jié)束時(shí)清空SecurityContextHolder |
| ConcurrentSessionFilter | 并發(fā)訪問(wèn)控制過(guò)濾器,主要功能:SessionRegistry中獲取SessionInformation來(lái)判斷session是否過(guò)期,從而實(shí)現(xiàn)并發(fā)訪問(wèn)控制。 |
| HeaderWriterFilter | 給http response添加一些Header |
| CsrfFilter | 跨域過(guò)濾器,跨站請(qǐng)求偽造保護(hù)Filter |
| LogoutFilter | 處理退出登錄的Filter |
| X509AuthenticationFilter | 添加X(jué)509預(yù)授權(quán)處理機(jī)制支持 |
| CasAuthenticationFilter | 認(rèn)證filter,經(jīng)過(guò)這些過(guò)濾器后SecurityContextHolder中將包含一個(gè)完全組裝好的Authentication對(duì)象,從而使后續(xù)鑒權(quán)能正常執(zhí)行 |
| UsernamePasswordAuthenticationFilter | 認(rèn)證的filter,經(jīng)過(guò)這些過(guò)濾器后SecurityContextHolder中將包含一個(gè)完全組裝好的Authentication對(duì)象,從而使后續(xù)鑒權(quán)能正常執(zhí)行。表單認(rèn)證是最常用的一個(gè)認(rèn)證方式。 |
| BasicAuthenticationFilter | 認(rèn)證filter,經(jīng)過(guò)這些過(guò)濾器后SecurityContextHolder中將包含一個(gè)完全組裝好的Authentication對(duì)象,從而使后續(xù)鑒權(quán)能正常執(zhí)行 |
| SecurityContextHolderAwareRequestFilter | 此過(guò)濾器對(duì)ServletRequest進(jìn)行了一次包裝,使得request具有更加豐富的API |
| JaasApiIntegrationFilter | (JAAS)認(rèn)證方式filter |
| RememberMeAuthenticationFilter | 記憶認(rèn)證處理過(guò)濾器,即是如果前面認(rèn)證過(guò)濾器沒(méi)有對(duì)當(dāng)前的請(qǐng)求進(jìn)行處理,啟用了RememberMe功能,會(huì)從cookie中解析出用戶,并進(jìn)行認(rèn)證處理,之后在SecurityContextHolder中存入一個(gè)Authentication對(duì)象。 |
| AnonymousAuthenticationFilter | 匿名認(rèn)證處理過(guò)濾器,當(dāng)SecurityContextHolder中認(rèn)證信息為空,則會(huì)創(chuàng)建一個(gè)匿名用戶存入到SecurityContextHolder中 |
| SessionManagementFilter | 會(huì)話管理Filter,持久化用戶登錄信息,可以保存到session中,也可以保存到cookie或者redis中 |
| ExceptionTranslationFilter | 異常處理過(guò)濾器,主要攔截后續(xù)過(guò)濾器(FilterSecurityInterceptor)操作中拋出的異常。 |
| FilterSecurityInterceptor | 安全攔截過(guò)濾器類,獲取當(dāng)前請(qǐng)求url對(duì)應(yīng)的ConfigAttribute,并調(diào)用accessDecisionManager進(jìn)行訪問(wèn)授權(quán)決策。 |
spring security的默認(rèn)filter鏈:
SecurityContextPersistenceFilter
->HeaderWriterFilter
->LogoutFilter
->UsernamePasswordAuthenticationFilter
->RequestCacheAwareFilter
->SecurityContextHolderAwareRequestFilter
->SessionManagementFilter
->ExceptionTranslationFilter
->FilterSecurityInterceptor
在上節(jié)我們已分析了核心的filter源碼以及功能??苫乜瓷瞎?jié)源碼分析更加深入的了解各個(gè)filter工作原理。
總結(jié):
??在認(rèn)證和訪問(wèn)授權(quán)過(guò)程前,首先必須進(jìn)行WebSecurityConfigurer符合自身應(yīng)用的security Configurer,也要清楚filter鏈的先后順序,才能更好理解spring security的工作原理以及在項(xiàng)目中出現(xiàn)的問(wèn)題定位。了解完準(zhǔn)備工作,接下來(lái)將展開(kāi)對(duì)認(rèn)證和訪問(wèn)授權(quán)模塊的工作流程研究以及項(xiàng)目示例分析。最后如有錯(cuò)誤可評(píng)論告知。
最后可關(guān)注公眾號(hào),一起學(xué)習(xí)。加群,每天會(huì)分享干貨,還有學(xué)習(xí)視頻領(lǐng)?。?/p>