Spring Security特性(密码)

news/2024/11/8 4:59:53 标签: spring, hive, 数据库, 学习, 前端, javascript, java

Spring Security特性(密码)

Spring Security提供了对 认证(authentication) 的全面支持。认证是指我们如何验证试图访问特定资源的人的身份。一个常见的验证用户的方法是要求用户输入用户名和密码。一旦进行了认证,我们就知道了身份并可以执行授权。

Spring Security提供了对用户认证的内置支持。本节专门介绍通用的认证支持,适用于Servlet和WebFlux环境。请参阅 Servlet 和WebFlux的认证部分,了解每个技术栈所支持的细节。

密码存储

Spring SecurityPasswordEncoder 接口用于对密码进行单向转换,让密码安全地存储。鉴于 PasswordEncoder 是一个单向转换,当密码转换需要双向时(如存储用于验证数据库的凭证),它就没有用了。通常情况下,PasswordEncoder 用于存储在认证时需要与用户提供的密码进行比较的密码。

DelegatingPasswordEncoder

在Spring Security 5.0之前,默认的 PasswordEncoder NoOpPasswordEncoder,它需要纯文本密码。根据密码历史部分,你可能期望现在默认的 PasswordEncoder 是类似 BCryptPasswordEncoder 的东西。然而,这忽略了三个现实世界的问题。

  1. 许多应用程序使用旧的密码编码(password encode),不能轻易迁移。
  2. 密码存储的最佳实践将再次改变。
  3. 作为一个框架,Spring Security 不能频繁地进行破坏性的改变。

相反,Spring Security引入了 DelegatingPasswordEncoder,它通过以下方式解决了所有的问题。

  1. 确保通过使用当前的密码存储建议对密码进行编码。
  2. 允许验证现代和传统格式的密码。
  3. 允许在未来升级编码。

你可以通过使用 PasswordEncoderFactories 轻松构建 DelegatingPasswordEncoder 的实例。
Create Default DelegatingPasswordEncoder

java">PasswordEncoder passwordEncoder =
    PasswordEncoderFactories.createDelegatingPasswordEncoder();

另外,你也可以创建自己的自定义实例。
Create Custom DelegatingPasswordEncoder

java">String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("sha256", new StandardPasswordEncoder());

PasswordEncoder passwordEncoder =
    new DelegatingPasswordEncoder(idForEncode, encoders);
密码存储格式

密码的一般格式是:
DelegatingPasswordEncoder Storage Format

{id}encodedPassword

id 是一个标识符,用于查询应该使用哪个 PasswordEncoderencodedPassword 是所选 PasswordEncoder 的原始编码密码。id 必须在密码的开头,以 { 开始,以 } 结束。如果找不到 idid 将被设置为null。例如,下面可能是一个使用不同 id 值编码的密码列表。所有的原始密码都是 password。
DelegatingPasswordEncoder Encoded Passwords Example

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
{noop}password
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
第一个密码的 PasswordEncoder id为 bcrypt,encodedPassword 值为$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG。匹配时,它将委托给 BCryptPasswordEncoder
第二个密码的 PasswordEncoder id为 noop,encodedPassword 值为 password。匹配时,它将委托给 NoOpPasswordEncoder。
第三个密码的 PasswordEncoder id为 pbkdf2,encodedPassword 值为 5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc。匹配时,它将委托给 Pbkdf2PasswordEncoder。
第四个密码的 PasswordEncoder id为 scrypt,encodedPassword 值为 $e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= 。匹配时,它将委托给 SCryptPasswordEncoder。
最后一个密码的 PasswordEncoder id为 sha256,encodedPassword 值为 97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0。匹配时,它将委托给 StandardPasswordEncoder。

一些用户可能会担心,存储格式是为潜在的黑客提供的。这不是一个问题,因为密码的存储并不依赖于算法是一个秘密。此外,大多数格式在没有前缀的情况下,攻击者很容易搞清楚。例如,BCrypt密码经常以 $2a$ 开始。\

密码编码

传递给构造函数的 idForEncode 决定了哪一个 PasswordEncoder 被用于编码密码。在我们之前构建的 DelegatingPasswordEncoder 中,这意味着编码密码的结果被委托给 BCryptPasswordEncoder,并以 {bcrypt} 为前缀。最终的结果看起来像下面的例子。

DelegatingPasswordEncoder Encode Example

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
密码匹配(对比)

匹配是基于 {id} 和构造函数中提供的 id PasswordEncoder 的映射。我们在密码存储格式中的例子提供了一个如何实现的工作实例。默认情况下,用一个密码和一个没有映射的id(包括空id)调用 matches(CharSequence, String) 的结果是 IllegalArgumentException。这个行为可以通过使用 DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder) 来定制。

通过使用 id,我们可以在任何密码编码上进行匹配,但通过使用最现代的密码编码对密码进行编码。这一点很重要,因为与加密不同,密码散列(Hash)的设计使我们没有简单的方法来恢复明文。既然没有办法恢复明文,那么就很难迁移密码了。虽然用户迁移 NoOpPasswordEncoder 很简单,但我们选择默认包含它,以使它的入门体验更简单。

入门体验

如果你正在制作一个演示或样本,花时间对用户的密码进行哈希处理是有点麻烦的。有一些方便的机制可以使之更容易,但这仍然不是为生产准备的。

withDefaultPasswordEncoder Example

UserDetails user = User.withDefaultPasswordEncoder()
  .username("user")
  .password("password")
  .roles("user")
  .build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

如果你要创建多个用户,你也可以重复使用builder。

withDefaultPasswordEncoder Reusing the Builder

UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
  .username("user")
  .password("password")
  .roles("USER")
  .build();
UserDetails admin = users
  .username("admin")
  .password("password")
  .roles("USER","ADMIN")
  .build();

这确实对存储的密码进行了哈希处理,但密码仍然暴露在内存和编译后的源代码中。因此,对于生产环境来说,它仍然不被认为是安全的。对于生产来说,你应该在外部对你的密码进行散列(Hash)。

用Spring Boot CLI进行编码

对密码进行正确编码的最简单方法是使用 Spring Boot CLI

例如,下面的例子对 password 的密码进行编码,以便与 DelegatingPasswordEncoder 一起使用。

Spring Boot CLI encodepassword Example

spring encodepassword password
{bcrypt}$2a$10$X5wFBtLrL/kHcmrOGGTrGufsBX8CJ0WpQpF3pgeuxBB/H73BK1DW6
故障排除

如密码存储格式中所述,当被存储的密码之一没有 id 时,会出现以下错误。

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
	at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)
	at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)

解决这个问题的最简单方法是弄清楚你的密码目前是如何存储的,并明确地提供正确的 PasswordEncoder。

如果你是从Spring Security 4.2.x迁移过来的,你可以通过暴露一个 NoOpPasswordEncoder bean来恢复到以前的行为。

另外,你可以在所有的密码前加上正确的 id,并继续使用 DelegatingPasswordEncoder。例如,如果你使用的是BCrypt,你可以将你的密码从类似的地方迁移过来。

$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

迁移为如下:

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

关于映射的完整列表,请参见 PasswordEncoderFactories 的Javadoc。

BCryptPasswordEncoder

BCryptPasswordEncoder 的实现使用广泛支持的 bcrypt 算法对密码进行散列。为了使它对密码破解有更强的抵抗力,bcrypt故意做得很慢。像其他自适应单向函数一样,它应该被调整为在你的系统上验证一个密码需要1秒左右。BCryptPasswordEncoder 的默认实现使用 BCryptPasswordEncoder Javadoc 中提到的强度10。我们鼓励你在自己的系统上调整和测试强度参数,使其大约需要1秒钟来验证一个密码。

BCryptPasswordEncoder

// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
Argon2PasswordEncoder

Argon2PasswordEncoder 的实现使用 Argon2 算法对密码进行散列。Argon2密码哈希大赛 的冠军。为了打败定制硬件上的密码破解,Argon2是一种故意的慢速算法,需要大量的内存。像其他自适应单向函数一样,它应该被调整为在你的系统上验证一个密码需要1秒左右。 Argon2PasswordEncoder 的当前实现需要 BouncyCastle

Argon2PasswordEncoder

// Create an encoder with all the defaults
Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
Pbkdf2PasswordEncoder

Pbkdf2PasswordEncoder 的实现使用 PBKDF2 算法对密码进行散列。为了抵御密码破解,PBKDF2是一种故意的慢速算法。像其他自适应单向函数一样,它应该被调整为在你的系统上验证一个密码需要1秒左右。当需要FIPS认证时,这种算法是一个不错的选择。

Pbkdf2PasswordEncoder

// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
SCryptPasswordEncoder

SCryptPasswordEncoder的实现使用 scrypt 算法对密码进行散列。为了打败定制硬件上的密码破解,scrypt是一个故意的慢速算法,需要大量的内存。像其他自适应单向函数一样,它应该被调整为在你的系统上验证一个密码需要1秒左右。

SCryptPasswordEncoder

// Create an encoder with all the defaults
SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
其他 PasswordEncoder

有相当数量的其他 PasswordEncoder 实现,它们的存在完全是为了向后兼容。它们都被废弃了,表明它们不再被认为是安全的。然而,没有计划删除它们,因为迁移现有的遗留系统很困难。

密码存储配置

Spring Security 默认使用 DelegatingPasswordEncoder。然而,你可以通过将 PasswordEncoder 暴露为 Spring Bean 来进行定制。

如果你是从 Spring Security 4.2.x 迁移过来的,你可以通过暴露一个 NoOpPasswordEncoder Bean 来恢复到以前的行为。

恢复到 NoOpPasswordEncoder 被认为是 不安全的。你应该转而使用 DelegatingPasswordEncoder来支持安全的密码编码。

NoOpPasswordEncoder

@Bean
public static NoOpPasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}
<b:bean id="passwordEncoder"
    class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>

在XML 配置下,要求 NoOpPasswordEncoder Bean的名称为 passwordEncoder

更改密码配置

大多数允许用户指定密码的应用程序也需要一个更新密码的功能。

用于更改密码的 Well-Known URL 表示一种机制,密码管理器可以通过该机制发现特定应用程序的密码更新端点。

你可以配置 Spring Security 来提供这个发现端点。例如,如果你的应用程序中更改密码的端点是 /change-password,那么你可以这样配置 Spring Security

Default Change Password Endpoint

http
    .passwordManagement(Customizer.withDefaults())
<sec:password-management/>

然后,当密码管理器导航到 /.well-known/change-password 时,Spring Security 将重定向你的端点,/change-password

或者,如果你的端点是 /change-password 以外的东西,你也可以像这样指定。

Change Password Endpoint

http
    .passwordManagement((management) -> management
        .changePasswordPage("/update-password")
    )
<sec:password-management change-password-page="/update-password"/>

通过上述配置,当密码管理器导航到 /.well-known/change-password 时,那么 Spring Security 将重定向到 /update-password


http://www.niftyadmin.cn/n/5743256.html

相关文章

C++ 的异常处理详解

C 的异常处理详解 在编程过程中&#xff0c;错误和异常是不可避免的&#xff0c;合理的异常处理机制能够提高程序的健壮性。在 C 中&#xff0c;异常机制为捕获和处理错误提供了一种结构化的方式。本文将对 C 的异常处理进行详细探讨&#xff0c;包括异常的概念、如何抛出和捕…

用接地气的例子趣谈 WWDC 24 全新的 Swift Testing 入门(三)

概述 从 WWDC 24 开始&#xff0c;苹果推出了全新的测试机制&#xff1a;Swift Testing。利用它我们可以大幅度简化之前“老态龙钟”的 XCTest 编码范式&#xff0c;并且使得单元测试更加灵动自由&#xff0c;更符合 Swift 语言的优雅品味。 在这里我们会和大家一起初涉并领略…

压力测试,探索服务器性能瓶颈

什么是全链路压力测试&#xff1f; 全链路压力测试是指基于真实业务场景&#xff0c;通过模拟海量的用户请求&#xff0c;对整个后台服务进行压力测试&#xff0c;从而评估整个系统的性能水平。 创建全链路压力测试 第一步&#xff1a;准备测试数据 为了尽量模拟真实的业务…

RabbitMQ 高级特性——消息分发

文章目录 前言消息分发RabbitMQ 分发机制的应用场景1. 限流2. 负载均衡 前言 当 RabbitMQ 的队列绑定了多个消费者的时候&#xff0c;队列会把消息分发给不同的消费者&#xff0c;每条消息只会发送给订阅列表的一个消费者&#xff0c;但是呢&#xff0c;RabbitMQ 默认是以轮询…

Eslint 和 Prettier

提示&#xff1a;ESLint 和 Prettier 是两个常用的工具&#xff0c;它们在 JavaScript 生态系统中扮演着重要角色&#xff0c;但它们的功能和目的有所不同。 一、ESLint是什么&#xff1f; 1.目的&#xff1a; ESLint 是一个静态代码分析工具&#xff0c;主要用于查找和修复 …

WPF中的INotifyPropertyChanged接口

INotifyPropertyChanged 是一个在 WPF (Windows Presentation Foundation) 和 .NET 中使用的接口&#xff0c;它用于实现数据绑定时的数据更新通知。当实现了 INotifyPropertyChanged 接口的类的属性值发生变化时&#xff0c;这个接口允许对象通知绑定到该对象属性的 UI 元素&a…

【华为机试题】光伏场地建设规划 [Python]

题目 代码 class Solution:def func(self, input_args, area_list):count 0for i in range(input_args[0] - input_args[2] 1):for j in range(input_args[1] - input_args[2] 1):count 1 if self.area_compute(area_list,i,j,input_args[2],input_args[3]) else 0print(c…

Mac解决 zsh: command not found: ll

Mac解决 zsh: command not found: ll 文章目录 Mac解决 zsh: command not found: ll解决方法 解决方法 1.打开bash_profile 配置文件vim ~/.bash_profile2.在文件中添加配置&#xff1a;alias llls -alF键盘按下 I 键进入编辑模式3. alias llls -alF添加完配置后&#xff0c;按…