九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码

admin 2023年11月13日23:25:44评论14 views字数 19725阅读65分45秒阅读模式

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码

·前言·

Java Spring是目前企业开发中使用较多的一种java开发框架,因此基于该框架的安全内容尤为重要。Secure Code Warrior编码实验室的Java Spring涉及到的安全问题有9个,分别为缺少功能级别的访问控制、不恰当的身份验证、日志记录和监控不足、SQL注入、明文存储密码、路径遍历、服务器请求伪造、XML外部实体(XXE)、任意文件上传。


因涉及内容较多,完整内容将会在本公众号拆分为多篇内容分别发出。本文为该系列的第五篇——安全问题五:明文存储密码。


往期内容请查看Java Spring编码安全系列


Password hashing with Argon2.

使用 Argon2 进行密码哈希处理。

Migrate existing password hashes to Argon2 using Spring Security..

使用 Spring Security 将现有密码哈希迁移到 Argon2。


安全问题五:明文存储密码

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码




题目





1、介绍

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码

VikingBank is currently storing passwords in plain text. Bad practice, of course, as accounts are compromised from the moment anyone has access to the database. Moreover, people frequently reuse passwords for other accounts (don't do this!), which means those are compromised as well. 


Should we then encrypt passwords? No! Encryption is a two-way process. If the secret key is leaked, every encrypted password can be decrypted, and accounts can be compromised. 


If passwords need to be stored, the recommended way to do this is via hashing. This is a one-way process, meaning that the plain text password is scrambled into a unique value and can never be restored to the original state. Upon login, the submitted password is again hashed via the same function and then compared to the hash that was stored in the database. 


The objective of this lab is to migrate existing password hashes to Argon2 with the help of Spring Security's DelegatingPasswordEncoder. Let's get started!


VikingBank 目前以纯文本形式存储密码。当然,这是不好的做法,因为从任何人有权访问数据库的那一刻起,帐户就会受到威胁。此外,人们经常重复使用其他帐户的密码(不要这样做!),这意味着这些帐户也会受到损害。


那么我们应该加密密码吗?不!加密是一个双向过程。如果密钥泄露,每个加密的密码都可以被解密,帐户也可能被泄露。


如果需要存储密码,推荐的方法是通过散列。这是一个单向过程,意味着纯文本密码被打乱为唯一值,并且永远无法恢复到原始状态。登录后,提交的密码再次通过相同的函数进行哈希处理,然后与存储在数据库中的哈希值进行比较。


本实验的目标是借助 Spring Security 的 DelegatingPasswordEncoder 将现有密码哈希迁移到 Argon2。让我们开始吧!


前置科普

一、Argon2 介绍

1.Argon2相关

Argon2是一个密钥推导函数,在2015年7月被选为密码哈希大赛的冠军,它由卢森堡大学的Alex Biryukov、Daniel Dinu和Dmitry Khovratovich设计。Argon2的实现通常是以Creative Commons CC0许可(即公共领域)或Apache License 2.0发布,并提供了三个相关版本,分别是Argon2d,Argon2i和Argon2id。

  • Argon2d:最大限度地抵抗 GPU 破解攻击

  • Argon2i:针对侧信道攻击进行了优化

  • Argon2id:混合版本

推荐使用 Argon2id 版本的密码,因为它可以平衡侧通道和基于 GPU 的攻击。

Argon2id 的具体参数配置如下:

 Inputs:      password (P):       Bytes (0..232-1)    Password (or message) to be hashed      salt (S):           Bytes (8..232-1)    Salt (16 bytes recommended for password hashing)      parallelism (p):    Number (1..224-1)   Degree of parallelism (i.e. number of threads)      tagLength (T):      Number (4..232-1)   Desired number of returned bytes      memorySizeKB (m):   Number (8p..232-1)  Amount of memory (in kibibytes) to use      iterations (t):     Number (1..232-1)   Number of iterations to perform      version (v):        Number (0x13)       The current version is 0x13 (19 decimal)      key (K):            Bytes (0..232-1)    Optional key (Errata: PDF says 0..32 bytes, RFC says 0..232 bytes)      associatedData (X): Bytes (0..232-1)    Optional arbitrary extra data      hashType (y):       Number (0=Argon2d, 1=Argon2i, 2=Argon2id)   Output:      tag:                Bytes (tagLength)   The resulting generated bytes, tagLength bytes long

*左右滑动查看更多


Argon2id算法的具体原理可参考以下文章:

https://blog.csdn.net/superfjj/article/details/120392344

*左右滑动查看更多


2.密钥推导函数和Hash函数的区别

密钥推导函数通常用于密码学协议中生成和分发密钥,而哈希函数则广泛应用于数据完整性校验、消息认证码(MAC)生成、密码存储等方面。总的来说,密钥推导函数和哈希函数虽然都是密码学中常用到的函数,但它们在输入、输出、功能和应用场景等方面都有所不同,需要根据具体的使用场景和需求来选择合适的函数。

二、常见的hash加密方法介绍推荐

1.什么是hash算法

Hash算法(Hash Algorithm),也称为哈希算法、散列算法、杂凑算法、数据摘要算法,是一种用于将任意长度的数据(通常是消息或文本)转换成固定长度的二进制数据(哈希值)的数学函数。哈希算法的主要特点是,它会将不同长度的输入数据映射成相同长度的输出,通常是一个固定数量的比特(比如128位或256位)。

2.hash算法的主要用途

哈希算法的主要用途包括:

  1. 数据完整性检查:哈希算法用于检查数据是否在传输或存储过程中发生了变化。接收方可以计算接收到的数据的哈希值,并与发送方提供的哈希值进行比较,以验证数据的完整性。

  2. 密码哈希:哈希算法常用于将用户密码安全地存储在数据库中。用户的密码不会明文存储,而是存储其哈希值。当用户登录时,系统会将输入的密码哈希后与数据库中的哈希值进行比较。

  3. 数字签名:数字签名是一种安全机制,通过哈希算法生成消息的哈希值,然后使用私钥对哈希值进行签名。接收方可以使用发送方的公钥来验证签名和消息完整性。

  4. 数据结构:哈希算法常用于构建数据结构,如哈希表,用于高效地存储和检索数据。

  5. 数据加密:哈希算法在一些加密协议中用于生成密钥材料或检测数据完整性。

常见的哈希算法包括MD5、SHA(SHA-1、SHA-2、SHA-3)、SM3等。这些算法有不同的性能、安全性和用途,因此在具体应用中需要根据需求选择适当的哈希算法。哈希算法的一个关键特点是,即使输入数据发生微小的变化,其哈希值也会大幅度改变,因此哈希值的微小变化能够有效检测到数据的修改或篡改。


3.hash算法推荐

Argon2算法为密钥推导函数,其主要功能是生成密钥材料,而哈希函数的主要功能是将任意长度的消息转换为固定长度的哈希值。

密钥推导函数需要满足一些重要的安全特性,如不可逆性、强扩散性和随机性,而哈希函数则主要关注其安全性(如抗碰撞性和抗碰撞性)。

将Argon2算法与其他一些常见密码哈希算法(MD5、SHA-1、SHA-256)之间的优劣势进行对比:

特性 Argon2 MD5 SHA-1 SHA-256
抵抗暴力破解攻击
参数可调性
抵抗彩虹表攻击
随机盐值 可加 可加 可加 可加
防止时间攻击 高(Argon2i)
抵抗侧信道攻击 考虑(Argon2) 未考虑 未考虑 未考虑
广泛采用
安全性
灵活性

*左右滑动查看更多

MD5单位输入长度512位,输出128位,王小云教授的相关研究表明MD5的安全性已经不足。

SHA-1单位输入长度512位,输出160位,较MD5稍强。

SHA-256为SHA-2的一种,SHA-2已发布的版本还包括SHA-224、SHA-256、SHA-384、SHA-512等。

SHA-256单位输入长度512位,输出256位。

SM3为国家密码管理局公布的商用密码杂凑算法标准,安全性等同于SHA-256。SM3单位输入长度512位,输出256位。从hash算法的算法强度和安全能力来说,MD5等安全性已经不足的hash函数不推荐使用;SHA-1虽然安全性比Md5强,但也不推荐使用;SHA-256的安全性足以提供大量保护,广泛应用于数据完整性校验和密码存储等场合,是推荐使用的;SM3为国家密码管理局公布的商用密码杂凑算法标准,安全性等同于SHA-256,也是推荐使用的;SHA-384、SHA-512函数等虽然会提供更强的安全保障,但一般情况下性能没有SHA-256好,需要根据实际情况进行考虑;SHA-3则提供了更高的安全性,并具有多变体,能够适应不同的应用需求,但其运算速度较SHA-2慢;Argon2则是一种具有高内存填充率和防止tradeoff attacks的密码哈希函数,适用于密码存储和其他基于密码的密钥推导算法。

还需要注意的是,选择哪种hash算法需要综合考虑安全性、效率和应用需求等因素。同时,为了保证安全性,建议定期评估和更新hash算法,以应对新的安全威胁和挑战。


参考链接及推荐阅读:

https://zhuanlan.zhihu.com/p/103585966Argon2算法简介
https://blog.csdn.net/superfjj/article/details/120392344           密码学系列之:Argon2加密算法详解
https://blog.csdn.net/m0_69860228/article/details/124767498Java Spring中的三个密码加密库包

*左右滑动查看更多




2、源码

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码


PasswordConfig.java
package vikingbank.web;
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.crypto.password.NoOpPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;
@SuppressWarnings("deprecation")@Configuration@EnableWebSecuritypublic class PasswordConfig { @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); }}

*左右滑动查看更多

文件结构:

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码




3、步骤一 实现委托密码编码器

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码


该应用程序当前正在使用 NoOpPasswordEncoder。此已弃用的编码器不会对传入密码执行任何操作,这将生成纯文本密码。更改为新的密码编码器时,数据库中现有的密码哈希仍将使用旧的哈希格式,导致验证流程失败。Spring Security 具有 DelegatingPasswordEnco·der 来支持多个密码哈希。


简而言之,DelegatingPasswordEncoder:

  • 向密码散列添加前缀:{algorithm}hash。

  • 密码验证时,会查找该前缀并使用匹配的密码编码器。

首先,导航到分配文件夹中的PasswordConfig.java。


3.1 task1

  • 将passwordEncoder bean 的返回类型更改为DelegatingPasswordEncoder。

  • 删除当前的noop实现。

  • 在bean内部,创建一个HashMap<String, PasswordEncoder>。

Task Solution
@Beanpublic DelegatingPasswordEncoder passwordEncoder() { Map<String, PasswordEncoder> encoders = new HashMap<>(); // implement the other tasks}

*左右滑动查看更多


根据要求修改代码:

PasswordConfig.java
package vikingbank.web;
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.DelegatingPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import java.util.HashMap;import java.util.Map;
@Configuration@EnableWebSecuritypublic class PasswordConfig { @Bean public PasswordEncoder passwordEncoder() { Map<String, PasswordEncoder> encoders = new HashMap<>(); // 添加其他需要的密码编码器 encoders.put("noop", NoOpPasswordEncoder.getInstance());
return new DelegatingPasswordEncoder("noop", encoders); }}

*左右滑动查看更多


3.2 task2

接下来,向 HashMap 添加一个条目,其中noop作为键,NoOpPasswordEncoder 的实例作为其值。


3.3 task3

返回一个新的 DelegatingPasswordEncoder。它需要两个参数,第一个是默认密码编码器的密钥。使用 noop,因为这是唯一的条目。第二个参数应该是新创建的 HashMap。

Step Solution
@Beanpublic DelegatingPasswordEncoder passwordEncoder() { Map<String, PasswordEncoder> encoders = new HashMap<>(); encoders.put("noop", NoOpPasswordEncoder.getInstance());
return new DelegatingPasswordEncoder("noop", encoders);}
import java.util.HashMap;import java.util.Map;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;

*左右滑动查看更多


按照要求修改:

PasswordConfig.java
package vikingbank.web;
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.crypto.password.DelegatingPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import java.util.HashMap;import java.util.Map;
@Configuration@EnableWebSecuritypublic class PasswordConfig { /** * 创建密码编码器的Bean。 * @return 密码编码器。 */ @Bean public DelegatingPasswordEncoder passwordEncoder() { // 创建一个Map来存储密码编码器 Map<String, PasswordEncoder> encoders = new HashMap<>(); // 将"noop"密码编码器添加到Map中,使用NoOpPasswordEncoder.getInstance() // 这个密码编码器不执行任何实际的编码操作 encoders.put("noop", NoOpPasswordEncoder.getInstance()); // 创建一个DelegatingPasswordEncoder实例 // 并将初始密码编码器设置为"noop",使用提供的encoders Map // DelegatingPasswordEncoder根据提供的编码ID,将密码编码和解码委托给相应的编码器 return new DelegatingPasswordEncoder("noop", encoders); }}

*左右滑动查看更多


分析一下。


PasswordConfig 类是一个配置类,用于配置应用程序中用于身份验证的密码编码器。 


@Configuration 注解表示这是一个用于配置 bean 的类。 


@EnableWebSecurity 注解启用了 Spring Security 对 Web 应用程序的支持。


passwordEncoder 方法用 @Bean 注解表示创建并配置了一个名为 passwordEncoder 的 Bean,它是一个 DelegatingPasswordEncoder 类型的实例,用于密码的编码和解码。


在方法内部,首先创建一个 Map 对象 encoders 用于存储密码编码器。


然后,将 "noop" 作为键,使用 NoOpPasswordEncoder.getInstance() 方法创建一个密码编码器实例,并将其添加到 encoders Map 中。这个密码编码器不执行任何实际的编码操作,即密码以明文形式存储。


最后,使用初始密码编码器设置为 "noop",并提供 encoders Map,创建一个新的 DelegatingPasswordEncoder 实例。DelegatingPasswordEncoder 类根据提供的编码 ID,将密码编码和解码委托给相应的密码编码器。


需要注意的是,在生产环境中,不建议使用 NoOpPasswordEncoder,因为它以明文形式存储密码。这里仅作为示例使用,请在实际应用中选择安全的密码编码器。




4、步骤二 Add Argon2

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码


目前,唯一注册的密码编码器是 NoOpPasswordEncoder,它将以纯文本形式保存密码。最佳实践要求使用强大的哈希算法对敏感数据进行哈希处理,例如赢得密码哈希竞赛的 Argon2。


4.1 task1

  • 返回到上次停下的地方并向HashMap 添加一个新条目。

  • 为新条目指定一个引用argon2 的密钥。

  • 传入Argon2 实现:Argon2PasswordEncoder 提供开箱即用的安全实现:defaultsForSpringSecurity_v5_8() 。

  • 返回DelegatingPasswordEncoder 时,将Argon2 设置为默认密码编码器,而不是noop。

Step Solution
@Override public List<Invoice> filterReceivedInvoicesByAccountNumber(String bankAccountFilter, long bankAccountId) { var query = String.join(" ", "SELECT i.* FROM invoice AS i", "JOIN bank_account AS ba ON i.seller_id = ba.id AND i.buyer_id = ?",                "WHERE ba.account_number LIKE CONCAT('%', ?, '%')");
return jdbcTemplate.query(query, Invoice::fromRow, bankAccountId, bankAccountFilter); }

*左右滑动查看更多


直接加上即可。

PasswordConfig.java
package vikingbank.web;
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;import org.springframework.security.crypto.password.DelegatingPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.crypto.password.NoOpPasswordEncoder;import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import java.util.HashMap;import java.util.Map;
@Configuration@EnableWebSecuritypublic class PasswordConfig {
/** * 创建密码编码器的Bean。 * @return 密码编码器。 */ @Bean public DelegatingPasswordEncoder passwordEncoder() { // 创建一个Map来存储密码编码器 Map<String, PasswordEncoder> encoders = new HashMap<>(); // 将"noop"密码编码器添加到Map中,使用NoOpPasswordEncoder.getInstance() // 这个密码编码器不执行任何实际的编码操作,即密码以明文形式存储        encoders.put("noop"NoOpPasswordEncoder.getInstance());
// 将"argon2"密码编码器添加到Map中,使用Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8() // 这个密码编码器使用Argon2算法对密码进行编码和解码 encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()); // 创建一个DelegatingPasswordEncoder实例 // 并将初始密码编码器设置为"argon2",使用提供的encoders Map // DelegatingPasswordEncoder根据提供的编码ID,将密码编码和解码委托给相应的编码器 return new DelegatingPasswordEncoder("argon2", encoders); }}

*左右滑动查看更多




5、 步骤三 设置密码迁移

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码


新用户的密码现在通过 Argon2 算法进行哈希处理。但是,现有用户的密码仍以纯文本形式保存。这些密码应该重新哈希。


DelegatingPasswordEncoder 通过哈希前缀确定要使用哪个密码编码器。该前缀与 DelegatingPasswordEncoder 的 HashMap 条目的键匹配。例如,当 DelegatingPasswordEncoder 想要验证使用 NoOpPasswordEncoder 的密码时,哈希值将如下所示。

{noop}P@ssw0rd1


由于原始代码没有使用委托密码编码器,因此现有用户的密码哈希中没有此前缀。让我们先解决这个问题。


重要备注:

由于密码是纯文本形式,因此可以直接使用 Argon2 对密码进行哈希处理。但是,当密码已经经过哈希处理时,这将不起作用。本实验指导了一种适用于这两种情况的实践。



5.1 task1

转到 VikingBankAuthenticationProvider 并查看身份验证方法。

  • 创建检查以在身份验证期间验证密码散列是否具有前缀。实现此目的最简单的方法是通过正则表达式 (regex)。

  • 创建一个使用正则表达式“^{(noop|argon2)}”的模式来检查散列是否以 noop 或 argon2(这两个定义的密码编码器)开头。

Hint
Pattern pattern = Pattern.compile("^\{(noop|argon2)\}");

*左右滑动查看更多


5.2 task2

在哈希验证之前创建检查。当正则表达式找不到前缀时,这意味着这些哈希值是由旧的 NoOpPasswordEncoder 创建的。在用户哈希之前附加 {noop} 前缀,然后将其保存到数据库。

Hint
if (!pattern.matcher(user.getPassword()).find()) { // add the prefix}

*左右滑动查看更多


Step Solution
Pattern pattern = Pattern.compile("^\{(noop|argon2)\}");if (!pattern.matcher(user.getPassword()).find()) { user.setPassword("{noop}" + user.getPassword()); this.userRepository.save(user);}
import java.util.regex.Pattern;

*左右滑动查看更多


按照要求修改一下:

VikingBankAuthenticationProvider.java
package vikingbank.web;
import org.springframework.security.authentication.AuthenticationProvider;import org.springframework.security.authentication.BadCredentialsException;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.crypto.password.PasswordEncoder;import vikingbank.web.repositories.VikingBankUserRepository;
import java.util.List;import java.util.regex.Pattern;
public class VikingBankAuthenticationProvider implements AuthenticationProvider { private final VikingBankUserRepository userRepository; private final PasswordEncoder passwordEncoder;
public VikingBankAuthenticationProvider(VikingBankUserRepository userRepository, PasswordEncoder passwordEncoder) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; } /** * 对身份验证进行处理的方法。 * @param authentication 要验证的身份验证对象。 * @return 验证成功的身份验证对象。 * @throws AuthenticationException 如果身份验证失败。 */ @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 获取填写的用户名 var filledInName = authentication.getName(); // 根据用户名从用户存储库中获取用户 var user = userRepository.findVikingBankUserByEmail(filledInName); // 根据用户的权限列表创建 SimpleGrantedAuthority 对象列表 List<SimpleGrantedAuthority> authorities = user.getPrivileges() .stream() .map(p -> new SimpleGrantedAuthority(p.getName())) .toList(); // 创建一个 UserDetails 对象,用于身份验证成功后的用户信息 UserDetails userDetails = new User( user.getEmail(), user.getPassword(), true, true, true, true, authorities        );
// 创建一个正则表达式模式,用于检查密码哈希是否有前缀 Pattern pattern = Pattern.compile("^\{(noop|argon2)\}"); // 检查密码哈希是否以 noop 或 argon2 开头 if (!pattern.matcher(user.getPassword()).find()) { // 在用户哈希之前添加 {noop} 前缀 user.setPassword("{noop}" + user.getPassword()); this.userRepository.save(user); } // 使用密码编码器验证用户提供的凭据是否与存储的密码匹配 if (passwordEncoder.matches((String) authentication.getCredentials(), user.getPassword())) { // 创建一个 UsernamePasswordAuthenticationToken 对象,表示验证成功的身份验证对象 return new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), authorities); } // 如果凭据无效,抛出 BadCredentialsException 异常 throw new BadCredentialsException("Invalid credentials"); } /** * 检查此身份验证提供程序是否支持指定类型的身份验证对象。 * @param authentication 要检查的身份验证类。 * @return 如果支持该身份验证类,则返回 true;否则返回 false。 */ @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); }}

*左右滑动查看更多


提交通过。




6、步骤四 设置密码迁移

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码


现在,现有密码的哈希值中有一个前缀,并且 DelegatingPasswordEncoder 可以完全运行。唯一剩下的就是使用 Argon2 哈希算法重新哈希密码。值得一提的是,重新哈希必须在输入正确的密码后进行。


6.1 task1

导航到验证密码的检查。当密码哈希验证正确时,创建一个新的检查来验证密码哈希是否以 {noop} 开头。

Task Solution
if (passwordEncoder.matches((String)authentication.getCredentials(), user.getPassword())) { if (user.getPassword().startsWith("{noop}")) { // rehash the password } return new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), authorities);}

*左右滑动查看更多


6.2 task2

在这种情况下,请使用 passwordEncoder 字段通过 Argon2 重新哈希密码。


  • 在passwordEncoder 上,调用encode 方法,并传递用户的凭据。

  • 设置用户密码并传入新的哈希值。

  • 将用户对象保存到数据库中。

Task Solution
user.setPassword(this.passwordEncoder.encode((String)authentication.getCredentials()));this.userRepository.save(user);

*左右滑动查看更多

Step Solution
if (passwordEncoder.matches((String)authentication.getCredentials(), user.getPassword())) { if (user.getPassword().startsWith("{noop}")) { user.setPassword(this.passwordEncoder.encode((String)authentication.getCredentials())); this.userRepository.save(user); } return new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), authorities);}

*左右滑动查看更多


按照提示和要求整理到代码中:

VikingBankAuthenticationProvider.java
package vikingbank.web;
import org.springframework.security.authentication.AuthenticationProvider;import org.springframework.security.authentication.BadCredentialsException;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.crypto.password.PasswordEncoder;import vikingbank.web.repositories.VikingBankUserRepository;
import java.util.List;import java.util.regex.Pattern;
public class VikingBankAuthenticationProvider implements AuthenticationProvider { private final VikingBankUserRepository userRepository; private final PasswordEncoder passwordEncoder;
public VikingBankAuthenticationProvider(VikingBankUserRepository userRepository, PasswordEncoder passwordEncoder) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; } /** * 进行身份验证的方法 * @param authentication 要验证的身份验证对象 * @return 验证成功的身份验证对象 * @throws AuthenticationException 如果身份验证失败 */ @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 获取填写的用户名 var filledInName = authentication.getName(); // 根据用户名从用户存储库中获取用户信息        var user = userRepository.findVikingBankUserByEmail(filledInName);
// 根据用户的权限列表创建 SimpleGrantedAuthority 对象列表 List<SimpleGrantedAuthority> authorities = user.getPrivileges() .stream() .map(p -> new SimpleGrantedAuthority(p.getName()))                .toList();
// 创建一个 UserDetails 对象,用于身份验证成功后的用户信息 UserDetails userDetails = new User( user.getEmail(), user.getPassword(), true, true, true, true, authorities ); // 创建一个正则表达式模式,用于检查密码哈希是否有前缀        Pattern pattern = Pattern.compile("^\{(noop|argon2)\}");
// 检查密码哈希是否以 noop 或 argon2 开头 if (!pattern.matcher(user.getPassword()).find()) { // 在用户哈希之前添加 {noop} 前缀 user.setPassword("{noop}" + user.getPassword()); this.userRepository.save(user); } // 使用密码编码器验证用户提供的凭据是否与存储的密码匹配 if (passwordEncoder.matches((String)authentication.getCredentials(), user.getPassword())) { // 如果密码哈希以 {noop} 开头,表示使用了不安全的密码编码器 // 使用密码编码器重新编码用户提供的凭据,并保存到用户存储库中 if (user.getPassword().startsWith("{noop}")) { user.setPassword(this.passwordEncoder.encode((String)authentication.getCredentials())); this.userRepository.save(user); } // 创建一个 UsernamePasswordAuthenticationToken 对象,表示验证成功的身份验证对象 return new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), authorities); } // 如果凭据无效,抛出 BadCredentialsException 异常 throw new BadCredentialsException("Invalid credentials");    }
/** * 检查此身份验证提供程序是否支持指定类型的身份验证对象 * @param authentication 要检查的身份验证类 * @return 如果支持该身份验证类,则返回 true;否则返回 false */ @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); }}

*左右滑动查看更多


提交通过,分析一下。 


VikingBankAuthenticationProvider 类是一个自定义的身份验证提供程序,用于处理身份验证过程。


在构造函数中,通过依赖注入方式获取了 VikingBankUserRepository 和 PasswordEncoder 对象。


authenticate 方法是处理身份验证的核心方法。在方法中,首先获取填写的用户名,并通过用户名从用户存储库中获取用户信息。


然后,根据用户的权限列表创建了 SimpleGrantedAuthority 对象列表,表示用户的角色权限。


接下来,创建了一个 UserDetails 对象,用于身份验证成功后的用户信息。


紧接着,使用正则表达式模式检查用户的密码哈希是否有前缀。如果密码哈希不包含 "noop" 或 "argon2" 前缀,则在用户哈希之前添加 {noop} 前缀,并保存到用户存储库中。


使用密码编码器验证用户提供的凭据是否与存储的密码匹配。如果匹配成功,并且密码哈希以 {noop} 开头,表示使用了不安全的密码编码器,使用密码编码器重新编码用户提供的凭据,并保存到用户存储库中。


最后,创建一个 UsernamePasswordAuthenticationToken 对象,表示验证成功的身份验证对象。


如果凭据无效,抛出 BadCredentialsException 异常。


supports 方法用于检查此身份验证提供程序是否支持指定类型的身份验证对象,如果支持则返回 true,否则返回 false。在这里,该提供程序支持 UsernamePasswordAuthenticationToken 类型的身份验证对象。 

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码


总结



DelegatingPasswordEncoder 将为密码哈希添加前缀,以区分使用了哪个密码编码器。使用密码委托器的 Argon2 密码哈希可能如下所示:

 {argon2}$argon2id$v=19$m=15360,t=2,p=1$OC/O7Iw2Ro/wMgfUImj6bg$rRMvfWx7lVaZxOgbblGoBECzq31UsMm0jPzWjsZFJD4  

*左右滑动查看更多


使用 DelegatingPasswordEncoder 的优点是它允许应用程序轻松处理多种密码哈希算法,并方便迁移到较新的算法。建议使用强哈希算法,不要尝试编写自己的算法。


往期回顾

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码


关于安恒信息安全服务团队
安恒信息安全服务团队由九维安全能力专家构成,其职责分别为:红队持续突破、橙队擅于赋能、黄队致力建设、绿队跟踪改进、青队快速处置、蓝队实时防御,紫队不断优化、暗队专注情报和研究、白队运营管理,以体系化的安全人才及技术为客户赋能。

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码

九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码

原文始发于微信公众号(安恒信息安全服务):九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年11月13日23:25:44
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   九维团队-绿队(改进)| Java Spring编码安全系列之明文存储密码http://cn-sec.com/archives/2201777.html

发表评论

匿名网友 填写信息