Spring Boot Security原理及调用链路解析,代码讲解
# 1、启动原理
# 1.1 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
# 1.2 自动配置
Spring-boot-autoconfigure-2.4.1-source.jar/META-INF/spring.factories
# 1.3 注入Servlet容器Filter
@Bean
@ConditionalOnBean(name = "springSecurityFilterChain")
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration =
new DelegatingFilterProxyRegistrationBean("springSecurityFilterChain");
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}
创建DelegatingFilterProxy,并添加到ApplicationContextFacade.context.filterDefs
# 2、调用与配置
# 2.1 DelegatingFilterProxy
由DelegatingFilterProxyRegistrationBean对象创建
- 从spring容器中获取名称为springSecurityFilterChain的Filter作为代理实例并调用doFilter(...)方法
# 2.2 FilterChainProxy(springSecurityFilterChain)
由WebSecurity对象创建
HttpFirewall:spring|StrictHttpFirewall
RequestRejectedHandler:spring|DefaultRequestRejectedHandler
List<SecurityFilterChain>:DefaultSecurityFilterChain(由HttpSecurity创建)
HttpServletRequest:StrictFirewalledRequest
HttpServletResponse:FirewalledResponse
- 请求通过HttpFirewall过滤,被拒抛出RequestRejectedException异常
- 通过uri匹配SecurityFilterChain,获取其中过滤器列表,如果过滤器列表返回为空,则跳过
- 生成VirtualFilterChain,执行doFilter(...)
# 2.3 过滤器链
100: ChannelProcessingFilter
300: WebAsyncManagerIntegrationFilter
400: SecurityContextPersistenceFilter
500: HeaderWriterFilter
600: CorsFilter
700: CsrfFilter
800: LogoutFilter
900: OAuth2AuthorizationRequestRedirectFilter
1000: Saml2WebSsoAuthenticationRequestFilter
1100: X509AuthenticationFilter
1200: AbstractPreAuthenticatedProcessingFilter
J2eePreAuthenticatedProcessingFilter
RequestAttributeAuthenticationFilter
RequestHeaderAuthenticationFilter
WebSpherePreAuthenticatedProcessingFilter
X509AuthenticationFilter
1300: CasAuthenticationFilter
1400: OAuth2LoginAuthenticationFilter
1500: Saml2WebSsoAuthenticationFilter
1600: UsernamePasswordAuthenticationFilter
1800: OpenIDAuthenticationFilter
1900: DefaultLoginPageGeneratingFilter
2000: DefaultLogoutPageGeneratingFilter
2100: ConcurrentSessionFilter
2200: DigestAuthenticationFilter
2300: BearerTokenAuthenticationFilter
2400: BasicAuthenticationFilter
2500: RequestCacheAwareFilter
2600: SecurityContextHolderAwareRequestFilter
2700: JaasApiIntegrationFilter
2800: RememberMeAuthenticationFilter
2900: AnonymousAuthenticationFilter
3000: OAuth2AuthorizationCodeGrantFilter
3100: SessionManagementFilter
3200: ExceptionTranslationFilter
3300: FilterSecurityInterceptor
3400: SwitchUserFilter
// 系统默认HttpSecurity的默认配置
http
.csrf(withDefaults())
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling(withDefaults())
.headers(withDefaults())
.sessionManagement(withDefaults())
.securityContext(withDefaults())
.requestCache(withDefaults())
.anonymous(withDefaults())
.servletApi(withDefaults())
.logout(withDefaults())
.apply(new DefaultLoginPageConfigurer<>());
// 自定义HttpSecurity的默认配置
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();
}
# 3、过滤器
# 3.1 ChannelProcessingFilter
# 3.2 WebAsyncManagerIntegrationFilter
直接添加到HttpSecurity.filter中
在WebAsyncManager中添加SecurityContextCallableProcessingInterceptor
# 3.3 SecurityContextPersistenceFilter
通过SecurityContextConfigurer创建
SecurityContextRepository:HttpSecurity.securityContext()设置 | HttpSessionSecurityContextRepository
// 获取SecurityContext
SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);
// 保存SecurityContext
void saveContext(SecurityContext context, HttpServletRequest req, HttpServletResponse resp);
// 是否存在SecurityContext
boolean containsContext(HttpServletRequest request);
org.springframework.security.core.context.SecurityContextHolder
- 根据请求响应holder调用SecurityContextRepository生成SecurityContext
- 调用结束时调用SecurityContextRespository保存SecurityContext
# 3.4 HeaderWriterFilter
通过HeadersConfigurer创建
通过参数shouldWriteHeadersEagerly判断是执行前后写response.header信息
The default headers include are:
// CacheControlConfig -> CacheControlHeadersWriter
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
// ContentTypeOptionsConfig -> XContentTypeOptionsHeaderWriter
X-Content-Type-Options: nosniff
// HstsConfig -> HstsHeaderWriter
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
// FrameOptionsConfig -> XFrameOptionsHeaderWriter
X-Frame-Options: DENY
// XXssConfig -> XXssProtectionHeaderWriter
X-XSS-Protection: 1; mode=block
// HpkpConfig -> HpkpHeaderWriter
Public-Key-Pins-Report-Only | Public-Key-Pins: max-age=5184000;
pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=";
pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="
report-uri="https://example.com/pkp-report"
includeSubDomains
// ContentSecurityPolicyConfig -> ContentSecurityPolicyHeaderWriter
Content-Security-Policy | Content-Security-Policy-Report-Only: default-src 'self'
// ReferrerPolicyConfig -> ReferrerPolicyHeaderWriter
Referrer-Policy: origin
// FeaturePolicyConfig -> FeaturePolicyHeaderWriter
Feature-Policy:
# 3.5 LogoutFilter
通过LogoutConfigurer创建:HttpSecurity.logout()
LogoutSuccessHandler:configurer.logoutSuccessHandler(...)|SimpleUrlLogoutSuccessHandler
|DelegatingLogoutSuccessHandler
LogoutHandler:configurer.addLogoutHandler(...)|SecurityContextLogoutHandler
|LogoutSuccessEventPublishingLogoutHandler
RequestMatcher:configurer.logoutRequestMatcher(...)|OrRequestMatcher
- 根据RequestMatcher判断是否符合条件
- 先调用LogoutHandler处理登出逻辑
- 再调用LogoutSuccessHandler处理登出成功逻辑
# 3.6 UsernamePasswordAuthenticationFilter
通过FormLoginConfigurer创建:HttpSecurity.formLogin()
RequestMatcher:configurer.loginProcessingUrl(...)|AntPathRequestMatcher("/login", "POST")
usernameParameter:configurer.usernameParameter(...)|"username"
passwordParameter:configurer.passwordParameter(...)|"password"
postOnly:true
ApplicationEventPublisher:Spring
AuthenticationDetailsSource:configurer.authenticationDetailsSource(...)
|WebAuthenticationDetailsSource
AuthenticationManager:HttpSecurity.getSharedObject(AuthenticationManager.class)
MessageSourceAccessor:Spring
RememberMeServices:HttpSecurity.getSharedObject(RememberMeServices.class)
|NullRememberMeServices
continueChainBeforeSuccessfulAuthentication:false
SessionAuthenticationStrategy:HttpSecurity.getSharedObject(SessionAuthenticationStrategy.class)
|NullAuthenticatedSessionStrategy
allowSessionCreation:true
AuthenticationSuccessHandler:configurer.successHandler(...)
|SavedRequestAwareAuthenticationSuccessHandler
AuthenticationFailureHandler:configurer.failureHandler(...)|SimpleUrlAuthenticationFailureHandler
- 根据requiresAuthenticationRequestMatcher判断是否符合条件
- 根据postOnly判断是否仅仅支持POST请求(AuthenticationServiceException)
- 获取username、password创建UsernamePasswordAuthenticationToken
- 根据authenticationDetailsSource创建details并设置到UsernamePasswordAuthenticationToken中
- 根据authenticationManager执行认证并生成Authentication对象
- 根据sessionStrategy执行认证Authentication对象
- 根据rememberMeServices执行登录成功处理
- 根据eventPublisher发布InteractiveAuthenticationSuccessEvent事件
- 根据successHandler执行认证成功处理
# 3.7 AnonymousAuthenticationFilter
通过AnonymousConfigurer创建:HttpSecurity.anonymous()
AuthenticationDetailsSource:WebAuthenticationDetailsSource
key:UUID.randomUUID().toString()
principal:configurer.principal(...)|"anonymousUser"
authorities:configurer.authorities(...)|"ROLE_ANONYMOUS"
- 判断SecurityContextHolder.getContext().getAuthentication()是否为空
- 为空则创建AnonymousAuthenticationToken并放入缓存
# 3.8 ExceptionTranslationFilter
通过ExceptionHandlingConfigurer创建:HttpSecurity.exceptionHandling()
AccessDeniedHandler:configurer.defaultAccessDeniedHandlerFor(...)
|configurer.accessDeniedHandler(...)|configurer.accessDeniedPage(...)
|AccessDeniedHandlerImpl|RequestMatcherDelegatingAccessDeniedHandler
AuthenticationEntryPoint:configurer.authenticationEntryPoint(...)
|Http403ForbiddenEntryPoint|DelegatingAuthenticationEntryPoint
AuthenticationTrustResolver:AuthenticationTrustResolverImpl
ThrowableAnalyzer:DefaultThrowableAnalyzer
RequestCache:HttpSecurity.getSharedObject(RequestCache.class)|HttpSessionRequestCache
MessageSourceAccessor:SpringSecurityMessageSource.getAccessor()
- IOException直接抛出
- 根据throwableAnalyzer解析异常列表,解析出AuthenticationException|AccessDeniedException异常
- 没有上面两种异常直接抛出
- 根据authenticationEntryPoint处理AuthenticationException异常
- 判断是否是匿名用户,根据accessDeniedHandler处理AccessDeniedException异常
# 3.9 FilterSecurityInterceptor
通过ExpressionUrlAuthorizationConfigurer创建:HttpSecurity.authorizeRequests()
FilterInvocationSecurityMetadataSource:ExpressionBasedFilterInvocationSecurityMetadataSource
observeOncePerRequest:configurer.filterSecurityInterceptorOncePerRequest(...)|true
MessageSourceAccessor:Spring|SpringSecurityMessageSource.getAccessor()
ApplicationEventPublisher:Spring
AccessDecisionManager:configurer.accessDecisionManager(...)|AffirmativeBased
AfterInvocationManager:AfterInvocationProviderManager
AuthenticationManager:HttpSecurity.getSharedObject(AuthenticationManager.class)
|NoOpAuthenticationManager
RunAsManager:NullRunAsManager
alwaysReauthenticate:false
rejectPublicInvocations:false
validateConfigAttributes:true
publishAuthorizationSuccess:false
- 根据securityMetadataSource创建Collection<ConfigAttribute>(需要的权限)
- 根据accessDecisionManager判断当前用户是否有访问权限