当前位置: 首页 > article >正文

(3)spring security - 认识PasswordEncoder

目录

  • 1.简介
    • 1.1.简单了解认证流程
  • 2.密码验证
  • 3.PasswordEncoder的内置实现
  • 4.小结

目标:

  1. 简单了解认证的流程
  2. 简单认识spring security中的Password Encoder

1.简介

在这里插入图片描述
还是以这幅图为基础,认识Password Encoder到底是什么?

1.1.简单了解认证流程

在这里插入图片描述
当我们在登录表单上输入用户名和密码后,将会被UsernamePasswordAuthenticationFilter过滤器拦截,在public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)函数中,尝试从请求中获取用户名和密码。

//从请求中获取用户名
String username = obtainUsername(request);
username = (username != null) ? username.trim() : "";
//从请求中获取密码
String password = obtainPassword(request);
password = (password != null) ? password : "";

在这里插入图片描述
接着封装UsernamePasswordAuthenticationToken对象:

//将用户名和密码封装为未认证的UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,password);

在这里插入图片描述
设置details:

//设置authRequest对象中的details
setDetails(request, authRequest);

在这里插入图片描述
认证authRequest封装的信息:

this.getAuthenticationManager().authenticate(authRequest);

this.getAuthenticationManager()实际是获取一个AuthenticationManager类型的对象,返回的是ProviderManager类型的对象,并调用authenticate函数。当认证成功后会返回一个Authentication对象。

ProviderManager在Spring Security中扮演着认证提供者管理器的角色,其主要作用是管理和协调多个AuthenticationProvider的认证过程。ProviderManager是AuthenticationManager的一个实现,它通过使用一组AuthenticationProvider来处理认证请求。
其主要功能和作用包括:

  1. 认证过程管理‌:ProviderManager会遍历所有支持的AuthenticationProvider,找到第一个能够成功认证的AuthenticationProvider并返回填充更多信息的Authentication对象。如果某个AuthenticationProvider认证失败,ProviderManager会尝试下一个AuthenticationProvider,直到找到一个成功的认证结果或者所有Provider都尝试完毕‌。
  2. 异常处理‌:在认证过程中,如果某个AuthenticationProvider在认证过程中抛出AuthenticationException,ProviderManager会继续尝试下一个Provider。但如果抛出AccountStatusException或InternalAuthenticationServiceException,则会停止认证过程并抛出异常‌。
  3. 扩展性和灵活性‌:ProviderManager支持多种类型的AuthenticationProvider,包括数据库认证、LDAP认证等,这使得Spring Security能够处理不同类型的认证请求,增强了系统的灵活性和扩展性‌。
    在这里插入图片描述

由于我们现在使用的是数据库认证,所以实际使用的认证程序是DaoAuthenticationProvider:
在这里插入图片描述
实际调用的authenticate函数是AbstractUserDetailsAuthenticationProvider抽象类中的authenticate函数:

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		//......
		//获取你在登录表单中输入的用户名
		String username = determineUsername(authentication);
		//......
		//查询缓存中是否有匹配的用户信息,实际user为null
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
			cacheWasUsed = false;
			try {
				//使用retrieveUser函数,在数据库中获取用户信息
				//实际是使用我们自己在EmployeeDetailsService.java中定义的loadUserByUsername函数,
				//来获取数据库中的用户信息
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException ex) {
				//异常处理
			}
			//......
		}
		try {
			//检查账户是否锁定、账户是否可用、账户是否过期
			this.preAuthenticationChecks.check(user);
			//检查密码是否为null,如果密码不为null,获取密码并验证密码
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException ex) {
			//异常处理
		}
		//密码验证通过后,检查密码是否过期
		this.postAuthenticationChecks.check(user);
		//缓存用户信息
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}
		Object principalToReturn = user;
		//......
		//重新生成一个已经通过认证的UsernamePasswordAuthenticationToken(实际上它也是Authentication类型的),并返回它
		//最终的Authentication对象被返回给UsernamePasswordAuthenticationFilter过滤器的attemptAuthentication函数
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

接着执行AbstractAuthenticationProcessingFilterdoFilter函数。
在这里插入图片描述
doFilter函数中将会:

  1. 组合多个SessionAuthenticationStrategy实例,以提供更灵活和强大的会话管理功能。
  2. 在securitycontexholder上设置认证成功的身份验证对象
  3. 通知配置的RememberMeServices登录成功(当前没做任何事情)
  4. 通过配置的ApplicationEventPublisher触发一个InteractiveAuthenticationSuccessEvent
  5. 将其他行为委托给AuthenticationSuccessHandler。

最后回到FilterChainProxy,执行其它过滤器。

2.密码验证

上一节说到,密码的验证是由一下代码实现的:

//检查密码是否为null,如果密码不为null,获取密码并验证密码
//其中user保存的数据库中的用户信息数据
//authentication保存的是登录表单中输入的用户名和密码
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);

additionalAuthenticationChecks的源代码是:

	@Override
	@SuppressWarnings("deprecation")
	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		//判断密码是否为null
		if (authentication.getCredentials() == null) {
			//打印日志
			//抛出异常
		}
		//获取明文密码
		String presentedPassword = authentication.getCredentials().toString();
		//验证密码
		if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
			//密码验证失败
			//打印日志
			//抛出异常
		}
	}

其中的matches函数源码如下:

	@Override
	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		if (rawPassword == null) {
			throw new IllegalArgumentException("rawPassword cannot be null");
		}
		if (encodedPassword == null || encodedPassword.length() == 0) {
			this.logger.warn("Empty encoded password");
			return false;
		}
		//判断是否是BCrypt
		if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
			this.logger.warn("Encoded password does not look like BCrypt");
			return false;
		}
		//检查明文密码(登陆表单中输入的密码)是否与先前散列的密码(数据库中的密码)匹配
		return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
	}

checkpw实际就是比较两个字符串:

  1. 首先将明文密码编码成与数据库中的密码一样的格式
  2. 然后使用MessageDigest.isEqual方法,比较连个已编码的密码是否具有相同的长度并且对应位置的所有字节都相等

存储在数据库中的已编码的密码本身永远不会被解码!!!

3.PasswordEncoder的内置实现

我们可以在PasswordEncoderFactories类中找到spring security中支持的编码器的完整列表。如果其中一个符合我们的要求,我们就不需要自定义自己的编码器。

  • bcrypt - BCryptPasswordEncoder
  • ldap - org. springframework. security. crypto. password. LdapShaPasswordEncoder
  • MD4 - org. springframework. security. crypto. password. Md4PasswordEncoder
  • MD5 - new MessageDigestPasswordEncoder(“MD5”)
  • noop - org. springframework. security. crypto. password. NoOpPasswordEncoder:不编码密码,保持密码为明文状态。它只能用于单元测试。
  • pbkdf2 - Pbkdf2PasswordEncoder. defaultsForSpringSecurity_v5_5()
  • pbkdf2@SpringSecurity_v5_8 - Pbkdf2PasswordEncoder. defaultsForSpringSecurity_v5_8()
  • scrypt - SCryptPasswordEncoder. defaultsForSpringSecurity_v4_1()
  • scrypt@SpringSecurity_v5_8 - SCryptPasswordEncoder. defaultsForSpringSecurity_v5_8()
  • SHA-1 - new MessageDigestPasswordEncoder(“SHA-1”)
  • SHA-256 - new MessageDigestPasswordEncoder(“SHA-256”)
  • sha256 - org. springframework. security. crypto. password. StandardPasswordEncoder
  • argon2 - Argon2PasswordEncoder. defaultsForSpringSecurity_v5_2()
  • argon2@SpringSecurity_v5_8 - Argon2PasswordEncoder. defaultsForSpringSecurity_v5_8()

在上述的编码器中,建议使用bcrypt , pbkdf2 或scrypt 。

4.小结

本章以(2)Spring Security - 了解UserDetailsService的代码为基础,通过调试代码,简单学习了spring security的认证流程和密码的验证流程。

以后如果需要用到自定义的密码编码器,将另外写一篇学习笔记。


http://www.kler.cn/a/442803.html

相关文章:

  • Web 开发入门之旅:从静态页面到全栈应用的第一步
  • android framework.jar 在应用中使用
  • 宝塔面板使用 GoAccess Web 日志分析教程
  • ASP.NET Core 中使用 Cookie 身份验证
  • 数据分析-使用Excel透视图/表分析禅道数据
  • Gateway 网关
  • 大厂面试智力题大全(详细解题思路,持续更新)
  • 【map与set】—— 我与C++的不解之缘(二十二)
  • Redis内存淘汰策略有哪些
  • 算法刷题Day22:BM57 岛屿数量
  • UUG 深圳站 | Unity 6 新功能详细介绍和演示
  • 鸿蒙app封装 axios post请求失败问题
  • 《机器学习》3.7-4.3end if 启发式 uci数据集klda方法——非线性可分的分类器
  • 深度学习试题及答案解析(一)
  • linux minio安装
  • 网络编程中的黏包和半包问题
  • 【MySQL】优雅的使用MySQL实现分布式锁
  • Go语言后台实现选中式导出excel文件
  • 鸿蒙NEXT开发案例:颜文字搜索器
  • [bug] StarRocks borker load意向之外的bug
  • 《C 语言携手 PaddlePaddle C++ API:开启深度学习开发新征程》
  • SEO初学者-搜索引擎如何工作
  • 练习题:一维数组
  • pytest入门三:setup、teardown
  • 【WRF教程第3.3期】预处理系统 WPS 详解:以4.5版本为例
  • 第十四届蓝桥杯Scratch国赛真题—转动的车轮