Spring boot security详解

2022/1/20 javaspringspring security

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判断当前用户是否有访问权限