Authentication(身份认证框架)的架构
SecurityContextHolder
SpringSecurity会将用户的信息存储在SecurityContextHolder中,以下为SecurityContextHolder的模型
SecurityContextHolder中包含了SecurityContext对象,SecurityContext中包含了Authentication对象
Authentication对象的功能有两个:
- 作为后续验证的入参
- 获取当前验证通过的用户信息
Authentication包含三个属性:
- principal:用户身份,如果是用户/密码认证,这个属性就是UserDetails实例
- credentials:通常就是密码,在大多数情况下,在用户验证通过后就会被清除,以防密码泄露。
- authorities:用户权限
AuthenticationManager
- AuthenticationManager是用来实现身份认证的API接口,入参是Authentication,最常用的子类是ProviderManager
- AuthenticationProvider是某种具体的认证实现,例如DaoAuthenticationProvider用来实现用户/密码认证,JwtAuthenticationProvider实现JWT Token认证
- 支持多种类型AuthenticationProvider会注入到ProviderManager中,ProviderManager会根据Authentication的类型调用相应类型的AuthenticationProvider
AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter使用来进行用户认证的过滤器,会编排进SecurityFilterChain中,其处理流程为:
- 用户提交信息后,AbstractAuthenticationProcessingFilter 或从HttpServletRequest 提取信息并创建Authentication,Authentication的类型是由过滤器的类型决定的(AbstractAuthenticationProcessingFilter的子类),例如 UsernamePasswordAuthenticationFilter 创建 UsernamePasswordAuthenticationToken
- Authentication 被传入 the AuthenticationManager进行认证
- 如果认证失败
(1)SecurityContextHolder 清除掉
(2)RememberMeServices.loginFail 被调用,如果remember me 没有配置,则此方法为空方法
(3)AuthenticationFailureHandler 被调用 - 认证成功
SessionAuthenticationStrategy 被通知用户登陆.
Authentication 存入 SecurityContextHolder. 之后 SecurityContextPersistenceFilter 会将SecurityContext存入HttpSession.
RememberMeServices.loginSuccess 被调用,如果remember me没有配置,则改方法为空方法
ApplicationEventPublisher 发布 InteractiveAuthenticationSuccessEvent.
用户密码验证 DaoAuthenticationProvider
DaoAuthenticationProvider实现了接口AuthenticationProvider,并且会使用UserDetailsService和PasswordEncoder来验证用户和密码
- 身份认证的过滤器会提取请求信息中的用户密码信息,传递UsernamePasswordAuthenticationToken(包含用户名和密码)对象至AuthenticationManager,其中ProviderManager是AuthenticationManager接口的实现类
- ProviderManager 中注册了DaoAuthenticationProvider(AuthenticationProvider接口的一个实现类,用于用户密码认证)
- DaoAuthenticationProvider 调用UserDetailsService获取存储的用户信息,得到UserDetails对象,UserDetails提供了对用户信息的基本操作
- DaoAuthenticationProvider 调用PasswordEncoder对传入的密码进行加密,并与上一步得到的UserDetails中的密码进行比较
- 验证通过后,用户信息会被存储于SecurityContextHolder中
关于密码存储
在介绍SpringSecurity认证机制之前,先说一些关于密码存储的内容。在SpringSecurity中PasswordEncoder接口用来提供单向加密机制,使密码存储更加安全。
密码存储的历史
在过去的多年时间里,密码存储机制进化了多次。最开始,密码就是明文文本存储,例如存储在数据库中,获取密码则需要使用数据库的用户与密码,因此人们则认为这种方式是安全的,但一些恶意用户却可以轻易的破解此方式,例如使用SQL注入等手段。
此后,密码存储前会通过单向Hash加密,例如SHA-256,当一个用户申请认证时,系统会对用户输入的密码进行hash加密,然后与系统存储的加密密码进行比较,如此系统只需要存储加密后的密文,这样即使密码泄露,也不会泄露真实的密码。不幸的是,黑客们发明了彩虹表(Rainbow Tables),通过查询彩虹表,很多密码都可以轻易的破解。
为了降低彩虹表的能力,安全专家建议使用加盐密码(salted passwords),在密码进行单向hash加密时,加入一些随机字符,这些随机字符被称为盐(salt)。
但近些年来,随着计算机硬件的快速发展,hash运算速度已提升为每秒十亿级,这意味着单向hash加密机制已经不再安全。现在更加推荐使用自适应的单向加密机制,自适应的单向加密机制可以配置一个工作因子(work factor),该因子会跟随硬件设备性能自行调整,使得加密过程所需要的时间在一个固定的值,这个值推荐在1秒钟左右,如此破解密码变得更加困难,常见的自适应加密机制包括bcrypt, PBKDF2, scrypt, 和 argon2
Spring Scurity中的密码存储机制
在Spring Security 5.0以前,默认的PasswordEncoder是NoOpPasswordEncoder,即明文密码,现在Spring Security的默认PasswordEncoder是DelegatingPasswordEncoder,DelegatingPasswordEncoder可以代理多种加密机制,其实例化过程如下:
public class DelegatingPasswordEncoderTest {
public static void main(String[] args) {
String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("sha256", new StandardPasswordEncoder());
PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
//对密码进行加密
String encodedPassword = passwordEncoder.encode("password");
System.out.println(encodedPassword); //{bcrypt}$2a$10$8nfdxLtGwvDu4MRI.CpCmOg56d7zNyq2xVJqaO4b.OPDFjlsu47Am
//验证密码
boolean res = passwordEncoder.matches("password", "{bcrypt}$2a$10$550FMm3qENbQtIWOa6qfsuqBftBF3BfVibViHv2ueQ3wGs45jEPWu");
System.out.println("res = " + res); // res = true
}
}
密码的存储格式为,其中{id}为对应的加密算法:
{id}encodedPassword
以下为对字符串"password"加密后的各种算法的结果:
{bcrypt}$2a$10$4lVeyJaX4NeUGLcAOG1KsuhguK30dwxMt9vO4woC.5elEC2xufWSu
{noop}password
{pbkdf2}8c3682ac6c8a7285060cc984fe3cf6da2af1a98410ad244b8b2b90fb90f60665c437a5cff05682e3
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
{sha256}9b050796bcdab7d9f9009669fac770584071e4057f4edae723e28096fe21910e7e95420647a2749f
用户/密码认证
根据HttpRequest提取用户密码的方式区分
FormLogin 页面表单登陆
默认页面登陆认证
在Springboot中引入Spring Security依赖