spring security原理-学习笔记2-核心组件

  • 2019 年 10 月 28 日
  • 笔记

核心组件

AuthenticationManager,ProviderManager和AuthenticationProvider

AuthenticationManager只是一个接口,实际中是如何运作的?如果我们需要检查多个身份验证数据库或不同身份验证服务(如数据库和LDAP服务器)的组合,该怎么办?

Spring Security中的默认实现为ProviderManager,ProviderManager本身不处理身份验证请求,它会委托给AuthenticationProvider列表进行处理,每个列表都会被查询以查看它是否可以执行认证。每个提供程序将抛出异​​常或返回完全填充的Authentication对象。
org.springframework.security.authentication.ProviderManager内部实现:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {  遍历List<AuthenticationProvider> 列表{          if(provider.supports(toTest)){              result = provider.authenticate(authentication);              this.copyDetails(authentication, result);          }  }  this.parent.authenticate(authentication);  eventPublisher.publishAuthenticationSuccess(result);  }

还记得我们的好朋友UserDetails和UserDetailsService吗?验证身份验证请求的最常用方法是加载相应的UserDetails并检查加载的密码与用户输入的密码。这是DaoAuthenticationProvider使用的方法(见下文)。加载的UserDetails对象 – 特别是它包含的GrantedAuthority – 将在构建完全填充的Authentication对象时使用,该对象从成功的身份验证返回并存储在SecurityContext中。
DaoAuthenticationProvider继承org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider

public Authentication authenticate(Authentication authentication) throws AuthenticationException {  UserDetails user = this.userCache.getUserFromCache(username);  if(user==null){      user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);  }  this.preAuthenticationChecks.check(user);  this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);  this.postAuthenticationChecks.check(user);    Object principalToReturn = user.getUsername();  return this.createSuccessAuthentication(principalToReturn, authentication, user);  }

AuthenticationProvider实现类:org.springframework.security.authentication.dao.DaoAuthenticationProvider内部实现

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {  UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);  if (loadedUser == null) {     throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");  } else {     return loadedUser;  }  }

UserDetailsS​​ervice实现

In-Memory Authentication

<user-service id="userDetailsService">  <!-- Password以{noop}为前缀,指示将其委托给passwordencoder应该使用NoOpPasswordEncoder。这是不安全的生产,但使阅读样本中更容易。通常密码应该使用BCrypt散列 -->  <user name="jimi" password="{noop}jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />  <user name="bob" password="{noop}bobspassword" authorities="ROLE_USER" />  </user-service>

JdbcDaoImpl

从数据库加载

密码编码

历史的密码编码

  • 存储密码原始文本
  • 存储通过单向散列(如SHA-256)的密码
  • 为了降低Rainbow Tables的有效性,鼓励开发人员使用salted密码
  • 现在鼓励开发人员利用自适应单向函数来存储密码。开发人员来设定不同的worker factor满足不同的安全需要,在安全和效率之间做出权衡。例如: bcrypt , PBKDF2 , scrypt , and Argon2 .

DelegatingPasswordEncoder(spring5.0后的新特性)

  • 确保使用当前密码存储建议对密码进行编码
  • 允许验证现代和传统格式的密码
  • 允许将来升级编码

将编码方式存入密码文本

BCryptPasswordEncoder

https://en.wikipedia.org/wiki/Bcrypt

Pbkdf2PasswordEncoder

https://en.wikipedia.org/wiki/PBKDF2

SCryptPasswordEncoder

https://en.wikipedia.org/wiki/Scrypt