概述
SpringSecurity原理(一)——初探 SpringSecurity原理(二)——認證 SpringSecurity原理(三)——授權 SpringSecurity原理(四)——過濾器 SpringSecurity原理(五)——擴展與配置
前面的文章中我們已經基本瞭解了怎樣使用Spring Security的基本的認證(Authentication)和授權(Authority)
但是,我們還不清楚Spring Security到底是怎樣執行這些流程。
這篇文章,我們就以SpringBoot爲例,來梳理一下Spring Security從啓動到執行這些流程的過程。
前置知識
我們知道Spring Security是通過Filter的方式來完成它的核心流程。但是:
- Spring Security到底擁有哪些Filter?
- 這些Filter是如何注入容器?
- 我們如何自定義自己的Filter?
web.xml配置
前面我們已經介紹過了,最開始如果我們要配置Filter,一般是通過web.xml的方式:
<filter>
<filter-name>deleFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>spring-bean-name</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>deleFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
SpringBoot中添加Filter
在SpringBoot中可以通過@WebFilter和@ServletComponentScan註解,注入自定義的Filter。
@WebFilter(filterName = "myFilter",urlPatterns = "/*")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
}
@Override
public void destroy() {
}
}
@SpringBootApplication
@ServletComponentScan(basePackages = "vip.mycollege.filter")
public class StartApplication {
public static void main(String[] args) {
SpringApplication.run(StartApplication.class, args);
}
}
也可以通過FilterRegistrationBean的方式,注入自定義的Filter。
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new MyFilter());
bean.addUrlPatterns("/*");
return bean;
}
}
也可以通過DelegatingFilterProxyRegistrationBean的方式。
@Configuration
public class FilterConfig {
@Bean("proxyFilter")
public Filter filter (){
return new Filter() {
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
}
@Override
public void destroy() {
}
@Bean
public DelegatingFilterProxyRegistrationBean delegatingFilterProxyRegistrationBean(){
DelegatingFilterProxyRegistrationBean bean = new DelegatingFilterProxyRegistrationBean("proxyFilter");
bean.addUrlPatterns("/*");
return bean;
}
}
DelegatingFilterProxyRegistrationBean和FilterRegistrationBean都繼承了AbstractFilterRegistrationBean,從名字上看就知道是一個RegistrationBean,也就是說會在Servlet容器啓動的時候被注入。
DelegatingFilterProxyRegistrationBean會在Servlet容器中註冊一個DelegatingFilterProxy,用來代理Spring IoC容器中某個指定名稱的Filter bean。
FilterChainProxy
SpringBoot有一個SecurityFilterAutoConfiguration的自動配置類,就會配置一個name爲springSecurityFilterChain的DelegatingFilterProxyRegistrationBean,這個類的url-pattern默認爲/*,也就是說會過濾所有的請求。
name是springSecurityFilterChain是一個什麼鬼呢?
答案是:FilterChainProxy。
這個類是在HttpSecurityBeanDefinitionParser的registerFilterChainProxyIfNecessary方法中註冊。
HttpSecurityBeanDefinitionParser也是一個BeanDefinitionParser,因此它會通過parse方法來構建Filter類。
整個流程現在就清晰了:
- SpringBoot通過自動配置類搞了個DelegatingFilterProxyRegistrationBean
- DelegatingFilterProxyRegistrationBean會在Servlet啓動的時候註冊一個DelegatingFilterProxy
- DelegatingFilterProxy會默認會攔截所有的請求,然後交個一個別名爲springSecurityFilterChain的FilterChainProxy
- FilterChainProxy在持有一個SecurityFilterChain的list
- SecurityFilterChain本身又持有一個Filter列表,可以通過match找出url匹配的Request交個filters處理
FilterChainProxy除了持有過濾器,默認內置了一個StrictHttpFirewall一個HTTP防火牆,它採用了嚴格模式,遇到任何可疑的請求,會通過拋出異常RequestRejectedException拒絕該請求。
現在我們知道了Spring Security如何收集利用Filter了。
但是,Spring Security到底揹着我們弄了哪些Filter呢?
我只想說很多,要知道有哪些也很簡單,在FilterChainProxy打一個斷點,debug,看一下filterChains變量中的filters列表就能看到有哪些filter
默認情況下filterChains只有一個filte,就是DefaultSecurityFilterChain,看名字就知道這是一個SecurityFilterChain,他包含了一個Filter列表,默認有:
- WebAsyncManagerIntegrationFilter:與處理異步請求映射的 WebAsyncManager 進行集成
- SecurityContextPersistenceFilter: 請求前保存和請求後清除SecurityContextHolder中的安全上下文
- HeaderWriterFilter:將頭信息加入響應中
- CsrfFilter:處理跨站請求僞造
- LogoutFilter:處理登出
- UsernamePasswordAuthenticationFilter:處理基於表單的登錄
- DefaultLoginPageGeneratingFilter:如果沒有配置登錄頁,生成默認登錄頁
- DefaultLogoutPageGeneratingFilter:如果沒有登出頁,生成默認登出頁
- BasicAuthenticationFilter:處理HTTP BASIC認證
- RequestCacheAwareFilter:處理請求的緩存
- SecurityContextHolderAwareRequestFilter:包裝請求對象request
- AnonymousAuthenticationFilter:檢測SecurityContextHolder是否存在Authentication,如不存在提供一個匿名 Authentication
- SessionManagementFilter:管理 session 的過濾器
- ExceptionTranslationFilter:處理 AccessDeniedException 和 AuthenticationException 異常
- FilterSecurityInterceptor: 權限校驗相關
重要Filter
UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter本身沒啥好說的,它就是一個Filter,但是因爲它用得多,所以說一下。
Filter肯定先看doFilter方法,UsernamePasswordAuthenticationFilter的主要認證邏輯在attemptAuthentication:
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username : "";
username = username.trim();
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
很簡單,就是從request中獲取username和password的字段,封裝成UsernamePasswordAuthenticationToken,然後扔給AuthenticationManager去執行認證,當然,最終認證邏輯肯定是像DaoAuthenticationProvider 這樣的AuthenticationProvider執行。
FilterSecurityInterceptor
FilterSecurityInterceptor主要是用來做權限校驗的,具體的鑑權邏輯主要在AbstractSecurityInterceptor中。
FilterSecurityInterceptor也是一個Filter,所以,還是先看doFilter方法,調用了invoke:
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
//一次請求中避免重複檢查
if (isApplied(filterInvocation) && this.observeOncePerRequest) {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
return;
}
// 第一次調用,先設置標記,避免重複調用
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
// 業務邏輯調用之前,執行檢查鑑權操作主要就是在這裏面完成
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
// 執行具體的業務邏輯
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
}
finally {
super.finallyInvocation(token);
}
// 業務邏輯調用之後,主要是處理返回結果
super.afterInvocation(token, null);
}
FilterInvocation就是FilterInvocation、ServletResponse、FilterChain的簡單封裝。
我們看到整個invoke的邏輯非常清晰,很像AOP的around結構。
ExceptionTranslationFilter
ExceptionTranslationFilter的邏輯有點奇怪,它主要是爲了處理 AccessDeniedException 和 AuthenticationException 異常。但是並不是處理它前面產生的異常,而是它後面的Filter產生的異常,因爲它前面Filter如果異常了根本到不了它這裏。
它後面,默認就只有FilterSecurityInterceptor了,主要會產生AccessDeniedException授權異常,AuthenticationException是因爲有一個再認證的過程。
過濾器
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- ChannelProcessingFilter
- ConcurrentSessionFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- OAuth2AuthorizationRequestRedirectFilter
- Saml2WebSsoAuthenticationRequestFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- Saml2WebSsoAuthenticationFilter
- UsernamePasswordAuthenticationFilter
- ConcurrentSessionFilter
- OpenIDAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- DigestAuthenticationFilter
- BearerTokenAuthenticationFilter
- BasicAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- OAuth2AuthorizationCodeGrantFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- SwitchUserFilter
- FilterSecurityInterceptor