当前位置:   article > 正文

Spring Security框架原理及解析_found websecurityconfigureradapter as well as secu

found websecurityconfigureradapter as well as securityfilterchain. please se

1、简介

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

2、框架

2.1 组成

 FilterChainProxy:其作为servlet请求中过滤链当中的一个,其本身也是一个过滤链,继承自GenericFilterBean。

SercurityFilterChain:其作为安全的过滤链,内部也是由一系列的Filter组成。

2.2 创建

初始化链使用的Builder模式,当中使用Configurer来实现初始化及配置。Builer与Configurer的关系如下

SecurityBuilder:创建接口,方法为build。

AbstractSecurityBuilder:实现SecurityBuilder,实现了build方法,当中调用抽象方法doBuild,用于子类来实现具体的创建逻辑

AbstractConfiguredSecurityBuilder:继承自AbstractSecurityBuilder,实现了doBuild方法,提供了通过Configurer来初始化、配置SecurityBuilder,同时调用抽象performBuild方法执行构建,其由子类来实现。

WebSecurity:用于创建2.1中的FilterChainProxy

HttpSecurity:用于创建2.1中的SecurityChainFilter

WebSecurityConfigurerAdapter:作为WebSecurity的Configurer,用于添加HttpSecurity的Configurer

HttpSecurity**Configurer:主要是在org.springframework.security.config.annotation.web.configurers包下,用于配置HttpSecurity

2.2.1 创建实现

主要是在WebSecurityConfiguration中创建。

WebSecurityConfigurer的获取,通过Spring容器中获取WebSecurityConfigurer类型的bean,并对其作排序,然后添加到WebSecurity中

  1. @Autowired(required = false)
  2. public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
  3. ConfigurableListableBeanFactory beanFactory) throws Exception {
  4. this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
  5. if (this.debugEnabled != null) {
  6. this.webSecurity.debug(this.debugEnabled);
  7. }
  8. List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new AutowiredWebSecurityConfigurersIgnoreParents(
  9. beanFactory).getWebSecurityConfigurers();
  10. webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
  11. Integer previousOrder = null;
  12. Object previousConfig = null;
  13. for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
  14. Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
  15. if (previousOrder != null && previousOrder.equals(order)) {
  16. throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order
  17. + " was already used on " + previousConfig + ", so it cannot be used on " + config + " too.");
  18. }
  19. previousOrder = order;
  20. previousConfig = config;
  21. }
  22. for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
  23. this.webSecurity.apply(webSecurityConfigurer);
  24. }
  25. this.webSecurityConfigurers = webSecurityConfigurers;
  26. }

也可以自定义SecurityFilterChain

  1. @Autowired(required = false)
  2. void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
  3. this.securityFilterChains = securityFilterChains;
  4. }

也支持提供了WebSecurityCustomizer对WebSecurity定制化

  1. @Autowired(required = false)
  2. void setWebSecurityCustomizers(List<WebSecurityCustomizer> webSecurityCustomizers) {
  3. this.webSecurityCustomizers = webSecurityCustomizers;
  4. }

 创建FilterChainProxy的bean

  1. @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
  2. public Filter springSecurityFilterChain() throws Exception {
  3. boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
  4. boolean hasFilterChain = !this.securityFilterChains.isEmpty();
  5. Assert.state(!(hasConfigurers && hasFilterChain),
  6. "Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");
  7. if (!hasConfigurers && !hasFilterChain) {
  8. WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
  9. .postProcess(new WebSecurityConfigurerAdapter() {
  10. });
  11. this.webSecurity.apply(adapter);
  12. }
  13. for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
  14. this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
  15. for (Filter filter : securityFilterChain.getFilters()) {
  16. if (filter instanceof FilterSecurityInterceptor) {
  17. this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
  18. break;
  19. }
  20. }
  21. }
  22. for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
  23. customizer.customize(this.webSecurity);
  24. }
  25. return this.webSecurity.build();
  26. }

创建逻辑如下 

  • 在默认情况下,即没有配置WebSecurityConfigurer和SecurityFilterChain时,会创建默认的WebSecurityConfigurer.
  • 在有自定义SecurityFilterChain时,直接添加到webSecurity中,对于存在过滤器类型为FilterSecurityInterceptor的,取第一个作为webSecurity的securityInterceptor
  • 使用WebSecurityCustomizer定制化WebSecurity
  • 基于WebSecurityConfigurer创建FilterChainProxy

3、认证框架

其组件图为

  AbstractAuthenticationProcessingFilter:作为认证处理过滤器的基础框架。

AuthenticaionManager:是认证管理器接口,ProviderManager是其实现类,其依赖于AuthenticationProvider接口。

3.1 AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilters的处理流程为

3.2  AuthenticationManager的Builder和Configurer

此层的Builder接口为ProviderManagerBuilder。Builder与Configurer的关系图为

UserDetailsAwareConfigurer:继承SecurityConfigurerAdapter,提供了获取UserDetailsService的抽象方法getUserDetailsService

AbstractDaoAuthenticationConfigurer:继承UserDetailsAwareConfigurer,实现getUserDetailsService抽象方法

DaoAuthenticationConfigurer:继承AbstractDaoAuthenticationConfigurer,其配置泛型参数为自身DaoAuthenticationConfigurer

UserDetailsServiceConfigurer:继承AbstractDaoAuthenticationConfigurer,其配置泛型参数为C extends UserDetailsServiceConfigurer<B, C, U>

UserDetailsManagerConfigurer:继承UserDetailsServiceConfigurer,其U extends UserDetailsService对应参数为UserServiceManager。

InMemoryUserDetailsManagerConfigurer:继承UserDetailsManagerConfigurer

JdbcUserDetailsManagerConfigurer:继承UserDetailsManagerConfigurer

LdapAuthenticationProviderConfigurer:继承SecurityBuilderAdapter

3.3 AuthenticationManager初始化配置

AuthenticationManager的创建是通过AuthenticationConfiguration来配置的。AuthenticationProvider是通过InitializeAuthenticationProviderBeanManagerConfigurer,InitializeUserDetailsManagerConfigurer来添加的,其中InitializeAuthenticationProviderBeanManagerConfigurer用来添加AuthenticationProvider的Bean,InitializeUserDetailsManagerConfigurer用来添加包含UserDetailsService的DaoAuthenticationProvider。这两个Configurer都是继承自GlobalAuthenticationConfigurerAdapter。

其层次关系为

 创建AuthenticationManager是通过getAuthenticationManager方法来创建的

  1. public AuthenticationManager getAuthenticationManager() throws Exception {
  2. if (this.authenticationManagerInitialized) {
  3. return this.authenticationManager;
  4. }
  5. AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
  6. if (this.buildingAuthenticationManager.getAndSet(true)) {
  7. return new AuthenticationManagerDelegator(authBuilder);
  8. }
  9. for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
  10. authBuilder.apply(config);
  11. }
  12. this.authenticationManager = authBuilder.build();
  13. if (this.authenticationManager == null) {
  14. this.authenticationManager = getAuthenticationManagerBean();
  15. }
  16. this.authenticationManagerInitialized = true;
  17. return this.authenticationManager;
  18. }
  • 已经创建则直接返回authenticationManager.
  • 获取AuthenticationManagerBuilder的Bean, 如果buildingAuthenticationManager已经设置,则创建AuthenticationManagerDelegator处理并发情况
  • 收集GlobalAuthenticationConfigurerAdapter认证配置适配器,应用到AuthenticationManagerBuilder中。
  • 创建AuthenticationManager.

3.4 会话管理 

 授权时的会话管理是依赖SessionAuthenticationStrategy

3.4.1 会话认证策略

 RegisterSessionAuthenticationStrategy:负责在授权成功后,使用SessionRegistry注册管理用户

CsrfAuthenticationStrategy:用于删除旧的token,创建新的token

AbstractSessionFixationProtectionStrategy:处理会话固定攻击的基类

ChangeSessionIdAuthenticationStrategy:使用HttpServletRequest.changeSessionId修改sessionId来防止固定session攻击

SessionFixationProtectionStrategy:使用HttpServletRequest.invalidate来防止固定session攻击

CompositeSessionAuthenticationStrategy:组合一系列会话授权策略,内部维护了一个集合,保存多个不同的SessionAuthenticationStrategy。默认是(ConcurrentSessionControlAuthenticationStrategy,ChangeSessionIdAuthenticationStrategy, RegisterSessionAuthenticationStrategy)

ConcurrentSessionControlAuthenticationStrategy:处理session并发问题

NullAuthenticatedSessionStrategy:不做任何操作

3.4.2 会话元数据

SessionInformation用来记录会话信息。其定义为

  1. public class SessionInformation implements Serializable {
  2. private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
  3. private Date lastRequest;
  4. private final Object principal;
  5. private final String sessionId;
  6. private boolean expired = false;
  7. public SessionInformation(Object principal, String sessionId, Date lastRequest) {
  8. Assert.notNull(principal, "Principal required");
  9. Assert.hasText(sessionId, "SessionId required");
  10. Assert.notNull(lastRequest, "LastRequest required");
  11. this.principal = principal;
  12. this.sessionId = sessionId;
  13. this.lastRequest = lastRequest;
  14. }
  15. public void expireNow() {
  16. this.expired = true;
  17. }
  18. public Date getLastRequest() {
  19. return this.lastRequest;
  20. }
  21. public Object getPrincipal() {
  22. return this.principal;
  23. }
  24. public String getSessionId() {
  25. return this.sessionId;
  26. }
  27. public boolean isExpired() {
  28. return this.expired;
  29. }
  30. /**
  31. * Refreshes the internal lastRequest to the current date and time.
  32. */
  33. public void refreshLastRequest() {
  34. this.lastRequest = new Date();
  35. }
  36. }

3.4.3 会话注册

会话注册是通过SessionRegistry接口管理。其实现类为SessionRegistryImpl,用来维护SessionInformation数据。

3.4.4 会话管理触发时机

是在AbstractAuthenticationProcessingFilter的doFilter方法中

3.4.5 InvalidSessionStrategy

无效会话策略用于会话无效时的处理,是在SessionManagementFilter中使用。

3.4.6 SessionInformationExpiredStrategy

用在ConcurrentSessionFilter中的会话信息过期的处理

3.5 基于UserDetails的认证

AbstractUserDetailsAuthenticationProvider作为基于UserDetails的抽象基类,其提供了基础框架。

  1. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  2. String username = determineUsername(authentication);
  3. boolean cacheWasUsed = true;
  4. UserDetails user = this.userCache.getUserFromCache(username);
  5. if (user == null) {
  6. cacheWasUsed = false;
  7. try {
  8. user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
  9. }
  10. catch (UsernameNotFoundException ex) {
  11. if (!this.hideUserNotFoundExceptions) {
  12. throw ex;
  13. }
  14. throw new BadCredentialsException(this.messages
  15. .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
  16. }
  17. }
  18. try {
  19. this.preAuthenticationChecks.check(user);
  20. additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
  21. }
  22. catch (AuthenticationException ex) {
  23. if (!cacheWasUsed) {
  24. throw ex;
  25. }
  26. // There was a problem, so try again after checking
  27. // we're using latest data (i.e. not from the cache)
  28. cacheWasUsed = false;
  29. user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
  30. this.preAuthenticationChecks.check(user);
  31. additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
  32. }
  33. this.postAuthenticationChecks.check(user);
  34. if (!cacheWasUsed) {
  35. this.userCache.putUserInCache(user);
  36. }
  37. Object principalToReturn = user;
  38. if (this.forcePrincipalAsString) {
  39. principalToReturn = user.getUsername();
  40. }
  41. return createSuccessAuthentication(principalToReturn, authentication, user);
  42. }
  1. 首先从用户缓存里获取UserDetails
  2. 如果缓存里没有,调用抽象方法retrieveUser来获取,获取不到,会抛出BadCredentialsException异常
  3. 调用UserDetailsChecker来检查UseDetails,如果用户帐户锁了(isAccountNonLocked为 false)抛出LockedException,用户不可用(isEnabled为false)抛出DisabledException,用户过期(即isAccountNonExpired为false)抛出AccountExpiredException
  4. 调用additionalAuthenticationChecks抽象方法作附加的检查,由子类来实现
  5. 作后置授权检查,如果凭据过期(即isCredentialsNonExpired为false)抛出CredentialsExpiredException异常
  6. 创建新的Authentication

3.5.1 密码存储

密码通过PasswordEncoder加密后存储。其类层次图为

 DelegatingPasswordEncoder:代理模式的实现类

LazyPasswordEncoder:懒加载的实现类,其在AuthenticationConfiguration中使用。其首先看ApplicationContext中是否有对应的bean,如果有则直接使用,没有就使用PasswordEncoderFactories来创建默认的密码加密器。

3.6 RememberMeServices

用于在会话间记住用户身份,主要是通过发送Cookie给浏览器,在后续的会话中检测到此Cookie则自动登录。

其类图为

 AbstractRememberMeServices:作为RememberMeServices的抽象实现类,实现了接口,定义了处理的基本框架,暴露了抽象方法由子类来实现。其依赖于UserDetailsService接口

TokenBasedRememberMeServices:其基于hash的简单方法来处理。Cookie的生成方式为

  1. base64(urlencode(username)
  2. + ":"
  3. + urlencode(expiryTime)
  4. + ":"
  5. + urlencode(encodingAlgorithmName)
  6. + ":"
  7. +urlencode(encodingAlgorithm(username + ":" + tokenExpiryTime + ":" + password + ":" + key))
  8. )

PersistentTokenBasedRememberMeServices:其依赖PersistentTokenRepository接口用于token的持久化。支持两种持久化

  • InMemoryTokenRepositoryImpl内存中,主要用于测试
  • JdbcTokenRepositoryImpl 持久化在数据库中

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/67692
推荐阅读
相关标签
  

闽ICP备14008679号