前言
Spring Boot Security提供了:
- 认证(Authentication)
- 授权(Authorization)
- 提供常见攻击保护:Cors、
架构
`Spring Boot Security架构
是基于Servlet中的Filter实现的。整个Security是通过过滤器链(FilterChain)来完成认知、授权、攻击保护。
认证
认证管理(AuthenticationManager):
- 根据传入
Authentication
,通过AuthenticationProvider
来完成用户资源权限 - 根据用户认证情况,如果失败,发送publishAuthenticationFailure。如果成功,发送publishAuthenticationSuccess
认证提供者:AuthenticationProvider
- 根据传入Authentication,通过
UserDetailsService
获取到UserDetails - 根据UserDetails通过
PassswordEncorder
来完成密码校验 - 通过
UserDetailsChecker
来完成用户前置校验。如(用户是否禁用,是否被锁,凭证是否过期)。可选方法
用户信息服务:UserDetailsService
- 根据传入username获取到
UserDetails
决策
安全拦截器:AbstractSecurityInterceptor
- 获取安全元数据资源
SecurityMetadataSource
,一般是URL等信息 - 通过
AuthencationManager
来获得Authentication
,如果已存在则从上下文获取。 - 通过
AccessDecisionManger
来决策,是否通过。 - 实现类FilterSecurityInterceptor实现了
Filter
。
访问决策管理:AccessDecisionManager
- 通过AccessDecisionVoter中的
vote
投票决定是否通过。
访问决策投:AccessDecisionVoter
- 通过投票决定,返回拒绝访问(ACCESS_DENIED,-1)、弃权访问(ACCESS_DENIED,0)、准许访问(ACCESS_GRANTED,1);
Spring Security Web
AuthenticationFilter:认证过滤器
- 通过
RequestMatcher
校验当前请求是否需要执行当前过滤器。 - 通过
AuthenticationManagerResolver
获取AuthenticationManager
- 通过
AuthenticationManger
获取Authentication
。完成用户认证和获取用户信息 - 如果认证失败:通过
AuthenticationFailureHandler
返回失败信息 - 如果认知成功:通过
AuthenticationSuccessHandler
返回成功信息
类似AuthenticationFilter:功能类AbstractAuthenticationProcessingFilter
BasicAuthenticationFilter
:继承OncePerRequestFilterDigestAuthenticationFilter
:判断请求头:Authorization信息是否存在且以Digest 开头AbstractPreAuthenticatedProcessingFilter
:当前类是通过判断是否存在认证信息,判断是否执行
注意:OncePerRequestFilter,当前过滤器在一个Session只执行一次。
LogoutFilter:退出登录过滤器
- 通过
SecurityContext
获取上下文存储的Authentication
信息。 - 执行
LogoutHandler
中的logout
完成,退出逻辑处理。 - 退出处理完成之后,执行
LogoutSuccessHandler
中onLogoutSuccess
方法。完成退出成功后的处理
DefaultLoginPageGeneratingFilter:默认登录页通用过滤器
- 通过
doFilter
完成校验请求链接是否是:loginError
,logoutSuccess
,isLoginUrlRequest
,跳转到指定链接中。且为GET
请求。 - loginPageUrl 登录页面URL
- logoutSuccessUrl:登录成功URL
- failureUrl:登录失败URL
- formLoginEnabled: 表单登录是否启动
DefaultLogoutPageGeneratingFilter:默认登录退出通用页面
- 判断链接是否是
/logout
,且为GET
请求 - 是跳转到退出登录页面
一、Spring Boot Security启动原理
自动配置类
Spring Boot AutoConfiguration自动加载Security
Spring Boot AutoConfiguration项目下“META-INF/spring.factories"
提供了支持Servlet和Reactive两种Web类型的支持
#Security自动配置基于Servletorg.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
#UserDetailsService自动配置基于Servletorg.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
#安全过滤器自动配置基于Servletorg.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
SecurityAutoConfiguration(安全自动配置)
- 完成认证异常类,ApplicationEventPublisher
- 通过引入类:SpringBootWebSecurityConfiguration,WebSecurityEnablerConfiguration,SecurityDataConfiguration。
完成自动配置
@Configuration(proxyBeanMethods=false)/**
* 认证异常类的注册
*/@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)@EnableConfigurationProperties(SecurityProperties.class)@Import({SpringBootWebSecurityConfiguration.class,WebSecurityEnablerConfiguration.class,SecurityDataConfiguration.class})publicclassSecurityAutoConfiguration{@Bean@ConditionalOnMissingBean(AuthenticationEventPublisher.class)publicDefaultAuthenticationEventPublisherauthenticationEventPublisher(ApplicationEventPublisher publisher){returnnewDefaultAuthenticationEventPublisher(publisher);}}
SpringBootWebSecurityConfiguration(SpringBootWeb安全认证配置)
如果有一个bean类型是
WebSecurityConfigurationAdapter
, 添加·EnableWebSecurity·注解。主要将确保注解是存在的当为默认安全自动配置。而且如果用户添加了定制安全且忘记添加了当前注解。
如果EnableWebSecurity
已经添加了Bean或如果一个bean的名称是BeanIds#SPRING_SECURITY_FILTER_CHAIN
在当前用户的bean配置中,将不使用当前配置。
@Configuration(proxyBeanMethods=false)//Web安全配置适配器@ConditionalOnClass(WebSecurityConfigurerAdapter.class)@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)//当Web类型为Servlet时生效@ConditionalOnWebApplication(type=Type.SERVLET)publicclassSpringBootWebSecurityConfiguration{@Configuration(proxyBeanMethods=false)@Order(SecurityProperties.BASIC_AUTH_ORDER)staticclassDefaultConfigurerAdapterextendsWebSecurityConfigurerAdapter{}}
WebSecurityConfigurerAdapter(Web安全配置适配器)
- ContentNegotiationStrategy:内容协商策略
- AuthenticationConfiguration:认证配置
- AuthenticationManagerBuilder:认证管理器构建
- AuthenticationManager:认证管理
- FilterSecurityInterceptor:过滤器安全拦截器
- HttpSecurity:Http安全
@Order(100)publicabstractclassWebSecurityConfigurerAdapterimplementsWebSecurityConfigurer<WebSecurity>{privatefinalLog logger;privateApplicationContext context;/**
* 内容协商策略,决定媒体类型
* 请求头内容协商测试:accept
* 如果accept为空则,通过所有媒体类型,否则通过指定媒体类型。
* contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
*
*/privateContentNegotiationStrategy contentNegotiationStrategy;/**
* 对象后置处理器
*/privateObjectPostProcessor<Object> objectPostProcessor;/**
* 认证配置
*
* 原子性Boolean,用来保证构建认证管理,不会因多线程导致创建多个
* AtomicBoolean buildingAuthenticationManager = new AtomicBoolean();
* ApplicationContext applicationContext;
* 认证管理器
* 通过bean类中AuthenticationManagerBuilder创建
* AuthenticationManager authenticationManager;
* 认证管理器是否已经初始化
* boolean authenticationManagerInitialized;
* List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers = Collections.emptyList();
* 对象后置处理器
* ObjectPostProcessor<Object> objectPostProcessor;
*/privateAuthenticationConfiguration authenticationConfiguration;/**
* 认证管理器构建:用户获取用户认证管理
*
* 认证管理
* AuthenticationManager parentAuthenticationManager;
* 认证提供者
* List<AuthenticationProvider> authenticationProviders = new ArrayList();
* 用户详情服务
* UserDetailsService defaultUserDetailsService;
*
* 删除凭据(资源)
* Boolean eraseCredentials;
* 认证事件发布:一般为异常事件需要发布通知
* AuthenticationEventPublisher eventPublisher;
*/privateAuthenticationManagerBuilder authenticationBuilder;/**
*/privateAuthenticationManagerBuilder localConfigureAuthenticationBldr;//禁用本地配置认证构建者privateboolean disableLocalConfigureAuthenticationBldr;//认证管理器是否已初始化privateboolean authenticationManagerInitialized;/**
* 认证管理: 完成用户资源认证通过 AuthenticationProvider
* 尝试去认证通过Authentication对象,返回一个完全数据Authentication对象(包括允许权限)
* A AuthenticationManager在下列情况下必须抛出异常
* DisabledException:当前账号禁用时,抛出异常
* LockedException:当账号被锁定时,抛出异常
* BadCredentialsException:提供了不正确的凭据。向上抛出异常是可选的。
* 异常应该能别测试,且如果应用抛出异常应该按顺序抛出(如果账号别禁用或锁定,认证亲戚应该立即别拒绝,并在不执行凭据测试)。这是防止对禁用或锁定账号进行测试凭据。
*/privateAuthenticationManager;privateAuthenticationTrustResolver trustResolver;/**
* Http安全预防工具
* final HttpSecurity.RequestMatcherConfigurer requestMatcherConfigurer;
* 过滤器列表
* List<Filter> filters = new ArrayList();
* 请求匹配器,默认匹配所有请求
* RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
* 过滤器比较器
* FilterComparator comparator = new FilterComparator();
*/privateHttpSecurity http;privateboolean disableDefaults;
初始化,HttpSecurity前置构建行动
完成FilterSecurityInterceptor,过滤安全拦截器。完成对访问决策资源的验证
publicvoidinit(WebSecurity web)throwsException{HttpSecurity http=this.getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(()->{FilterSecurityInterceptor securityInterceptor=(FilterSecurityInterceptor)http.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);});}
获取HttpSecurity,在这个完成整个HttpSecurity的配置,包括登录、退出等。
protectedfinalHttpSecuritygetHttp()throwsException{if(this.http!=null){returnthis.http;}else{AuthenticationEventPublisher eventPublisher=this.getAuthenticationEventPublisher();this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);AuthenticationManager authenticationManager=this.authenticationManager();this.authenticationBuilder.parentAuthenticationManager(authenticationManager);Map<Class<?>,Object> sharedObjects=this.createSharedObjects();this.http=newHttpSecurity(this.objectPostProcessor,this.authenticationBuilder, sharedObjects);if(!this.disableDefaults){((HttpSecurity)((DefaultLoginPageConfigurer)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)this.http.csrf().and())//跨站点请求伪造.addFilter(newWebAsyncManagerIntegrationFilter())//Web异步管理拦截器过滤.exceptionHandling().and())//异常类过滤器设置.headers().and())//头过滤器设置.sessionManagement().and())//会话管理.securityContext().and())//安全上下文.requestCache().and())//请求缓存.anonymous().and())//匿名.servletApi().and())//api.apply(newDefaultLoginPageConfigurer())).and())//登录配置.logout();//退出登录ClassLoader classLoader=this.context.getClassLoader();List<AbstractHttpConfigurer> defaultHttpConfigurers=SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);Iterator var6= defaultHttpConfigurers.iterator();while(var6.hasNext()){AbstractHttpConfigurer configurer=(AbstractHttpConfigurer)var6.next();this.http.apply(configurer);}}this.configure(this.http);returnthis.http;}}
UserDetailsServiceAutoConfiguration(用户详情服务自动配置)
在所有Bean中未找到下列Bean则,加载当前配置
- AuthenticationManager:认证管理
- AuthenticationProvider:认证提供者
- UserDetailsService:用户详情服务
@Configuration(proxyBeanMethods=false)@ConditionalOnClass(AuthenticationManager.class)@ConditionalOnBean(ObjectPostProcessor.class)@ConditionalOnMissingBean(
value={AuthenticationManager.class,AuthenticationProvider.class,UserDetailsService.class},
type={"org.springframework.security.oauth2.jwt.JwtDecoder","org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector"})publicclassUserDetailsServiceAutoConfiguration{privatestaticfinalString NOOP_PASSWORD_PREFIX="{noop}";privatestaticfinalPattern PASSWORD_ALGORITHM_PATTERN=Pattern.compile("^\\{.+}.*$");privatestaticfinalLog logger=LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);@Bean@ConditionalOnMissingBean(
type="org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")@LazypublicInMemoryUserDetailsManagerinMemoryUserDetailsManager(SecurityProperties properties,ObjectProvider<PasswordEncoder> passwordEncoder){SecurityProperties.User user= properties.getUser();List<String> roles= user.getRoles();returnnewInMemoryUserDetailsManager(User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build());}privateStringgetOrDeducePassword(SecurityProperties.User user,PasswordEncoder encoder){String password= user.getPassword();if(user.isPasswordGenerated()){
logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));}if(encoder!=null|| PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()){return password;}return NOOP_PASSWORD_PREFIX+ password;}}
HttpSecurity
通过这个类,配置HttpSecurity。完成整个Filter链的构建。请求匹配,过滤比较.
配置示例
HttpSecurity http=newHttpSecurity(this.objectPostProcessor,this.authenticationBuilder, sharedObjects);if(!this.disableDefaults){((HttpSecurity)((DefaultLoginPageConfigurer)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)
http.csrf().and())//跨站点请求伪造.addFilter(newWebAsyncManagerIntegrationFilter())//Web异步管理拦截器过滤.exceptionHandling().and())//异常类过滤器设置.headers().and())//头过滤器设置.sessionManagement().and())//会话管理.securityContext().and())//安全上下文.requestCache().and())//请求缓存.anonymous().and())//匿名.servletApi().and())//api.apply(newDefaultLoginPageConfigurer())).and())//登录配置.logout();//退出登录