Spring Security特性(暗码)

打印 上一主题 下一主题

主题 1859|帖子 1859|积分 5577

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
Spring Security特性(暗码)

Spring Security提供了对 认证(authentication) 的全面支持。认证是指我们怎样验证试图访问特定资源的人的身份。一个常见的验证用户的方法是要求用户输入用户名和暗码。一旦举行了认证,我们就知道了身份并可以执行授权。
Spring Security提供了对用户认证的内置支持。本节专门先容通用的认证支持,适用于Servlet和WebFlux情况。请参阅 Servlet 和WebFlux的认证部分,了解每个技能栈所支持的细节。
暗码存储

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

在Spring Security 5.0之前,默认的 PasswordEncoder是  NoOpPasswordEncoder,它需要纯文本暗码。根据暗码汗青部分,你大概期望现在默认的 PasswordEncoder 是雷同 BCryptPasswordEncoder 的东西。然而,这忽略了三个现实天下的问题。

  • 许多应用步伐利用旧的暗码编码(password encode),不能轻易迁移。
  • 暗码存储的最佳实践将再次改变。
  • 作为一个框架,Spring Security 不能频繁地举行破坏性的改变。
相反,Spring Security引入了 DelegatingPasswordEncoder,它通过以下方式办理了全部的问题。

  • 确保通过利用当前的暗码存储建议对暗码举行编码。
  • 允许验证当代和传统格式的暗码。
  • 允许在将来升级编码。
你可以通过利用 PasswordEncoderFactories 轻松构建 DelegatingPasswordEncoder 的实例。
Create Default DelegatingPasswordEncoder
  1. PasswordEncoder passwordEncoder =
  2.     PasswordEncoderFactories.createDelegatingPasswordEncoder();
复制代码
另外,你也可以创建自己的自定义实例。
Create Custom DelegatingPasswordEncoder
  1. String idForEncode = "bcrypt";
  2. Map encoders = new HashMap<>();
  3. encoders.put(idForEncode, new BCryptPasswordEncoder());
  4. encoders.put("noop", NoOpPasswordEncoder.getInstance());
  5. encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
  6. encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
  7. encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
  8. encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
  9. encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
  10. encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
  11. encoders.put("sha256", new StandardPasswordEncoder());
  12. PasswordEncoder passwordEncoder =
  13.     new DelegatingPasswordEncoder(idForEncode, encoders);
复制代码
暗码存储格式

暗码的一般格式是:
DelegatingPasswordEncoder Storage Format
  1. {id}encodedPassword
复制代码
id 是一个标识符,用于查询应该利用哪个 PasswordEncoder,encodedPassword 是所选 PasswordEncoder 的原始编码暗码。id 必须在暗码的开头,以 { 开始,以 } 竣事。如果找不到  id,id 将被设置为null。比方,下面大概是一个利用不同 id 值编码的暗码列表。全部的原始暗码都是 password。
DelegatingPasswordEncoder Encoded Passwords Example
  1. {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
  2. {noop}password
  3. {pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
  4. {scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
  5. {sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
复制代码
  1. 第一个密码的 PasswordEncoder id为 bcrypt,encodedPassword 值为$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
  2. 。匹配时,它将委托给 BCryptPasswordEncoder
  3. 第二个密码的 PasswordEncoder id为 noop,encodedPassword 值为 password。匹配时,它将委托给 NoOpPasswordEncoder。
  4. 第三个密码的 PasswordEncoder id为 pbkdf2,encodedPassword 值为 5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc。匹配时,它将委托给 Pbkdf2PasswordEncoder。
  5. 第四个密码的 PasswordEncoder id为 scrypt,encodedPassword 值为 $e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= 。匹配时,它将委托给 SCryptPasswordEncoder。
  6. 最后一个密码的 PasswordEncoder id为 sha256,encodedPassword 值为 97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0。匹配时,它将委托给 StandardPasswordEncoder。
复制代码
一些用户大概会担心,存储格式是为潜伏的黑客提供的。这不是一个问题,由于暗码的存储并不依赖于算法是一个秘密。别的,大多数格式在没有前缀的情况下,攻击者很容易搞清楚。比方,BCrypt暗码常常以  $2a$ 开始。\
暗码编码

传递给构造函数的 idForEncode 决定了哪一个 PasswordEncoder 被用于编码暗码。在我们之前构建的 DelegatingPasswordEncoder 中,这意味着编码暗码的结果被委托给 BCryptPasswordEncoder,并以 {bcrypt} 为前缀。最终的结果看起来像下面的例子。
DelegatingPasswordEncoder Encode Example
  1. {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
  1. UserDetails user = User.withDefaultPasswordEncoder()
  2.   .username("user")
  3.   .password("password")
  4.   .roles("user")
  5.   .build();
  6. System.out.println(user.getPassword());
  7. // {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
复制代码
如果你要创建多个用户,你也可以重复利用builder。
withDefaultPasswordEncoder Reusing the Builder
  1. UserBuilder users = User.withDefaultPasswordEncoder();
  2. UserDetails user = users
  3.   .username("user")
  4.   .password("password")
  5.   .roles("USER")
  6.   .build();
  7. UserDetails admin = users
  8.   .username("admin")
  9.   .password("password")
  10.   .roles("USER","ADMIN")
  11.   .build();
复制代码
这确实对存储的暗码举行了哈希处理,但暗码仍然袒露在内存和编译后的源代码中。因此,对于生产情况来说,它仍然不被认为是安全的。对于生产来说,你应该在外部对你的暗码举行散列(Hash)。
用Spring Boot CLI举行编码

对暗码举行精确编码的最简单方法是利用 Spring Boot CLI。
比方,下面的例子对 password 的暗码举行编码,以便与 DelegatingPasswordEncoder 一起利用。
Spring Boot CLI encodepassword Example
  1. spring encodepassword password
  2. {bcrypt}$2a$10$X5wFBtLrL/kHcmrOGGTrGufsBX8CJ0WpQpF3pgeuxBB/H73BK1DW6
复制代码
故障排除

如暗码存储格式中所述,当被存储的暗码之一没有 id 时,会出现以下错误。
  1. java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
  2.         at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)
  3.         at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)
复制代码
办理这个问题的最简单方法是弄清楚你的暗码现在是怎样存储的,并明确地提供精确的 PasswordEncoder。
如果你是从Spring Security 4.2.x迁移过来的,你可以通过袒露一个 NoOpPasswordEncoder bean来恢复到从前的行为。
另外,你可以在全部的暗码前加上精确的 id,并继承利用 DelegatingPasswordEncoder。比方,如果你利用的是BCrypt,你可以将你的暗码从雷同的地方迁移过来。
  1. $2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
复制代码
迁移为如下:
  1. {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
复制代码
关于映射的完备列表,请拜见 PasswordEncoderFactories 的Javadoc。
BCryptPasswordEncoder

BCryptPasswordEncoder 的实现利用广泛支持的 bcrypt 算法对暗码举行散列。为了使它对暗码破解有更强的抵抗力,bcrypt故意做得很慢。像其他自顺应单向函数一样,它应该被调整为在你的系统上验证一个暗码需要1秒左右。BCryptPasswordEncoder 的默认实现利用 BCryptPasswordEncoder 的 Javadoc 中提到的强度10。我们鼓励你在自己的系统上调整和测试强度参数,使其大约需要1秒钟来验证一个暗码。
BCryptPasswordEncoder
  1. // Create an encoder with strength 16
  2. BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
  3. String result = encoder.encode("myPassword");
  4. assertTrue(encoder.matches("myPassword", result));
复制代码
Argon2PasswordEncoder

Argon2PasswordEncoder 的实现利用 Argon2 算法对暗码举行散列。Argon2是 暗码哈希大赛 的冠军。为了打败定制硬件上的暗码破解,Argon2是一种故意的慢速算法,需要大量的内存。像其他自顺应单向函数一样,它应该被调整为在你的系统上验证一个暗码需要1秒左右。 Argon2PasswordEncoder 的当前实现需要  BouncyCastle。
Argon2PasswordEncoder
  1. // Create an encoder with all the defaults
  2. Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
  3. String result = encoder.encode("myPassword");
  4. assertTrue(encoder.matches("myPassword", result));
复制代码
Pbkdf2PasswordEncoder

Pbkdf2PasswordEncoder 的实现利用  PBKDF2 算法对暗码举行散列。为了抵抗暗码破解,PBKDF2是一种故意的慢速算法。像其他自顺应单向函数一样,它应该被调整为在你的系统上验证一个暗码需要1秒左右。当需要FIPS认证时,这种算法是一个不错的选择。
Pbkdf2PasswordEncoder
  1. // Create an encoder with all the defaults
  2. Pbkdf2PasswordEncoder encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
  3. String result = encoder.encode("myPassword");
  4. assertTrue(encoder.matches("myPassword", result));
复制代码
SCryptPasswordEncoder

SCryptPasswordEncoder的实现利用 scrypt 算法对暗码举行散列。为了打败定制硬件上的暗码破解,scrypt是一个故意的慢速算法,需要大量的内存。像其他自顺应单向函数一样,它应该被调整为在你的系统上验证一个暗码需要1秒左右。
SCryptPasswordEncoder
  1. // Create an encoder with all the defaults
  2. SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();
  3. String result = encoder.encode("myPassword");
  4. assertTrue(encoder.matches("myPassword", result));
复制代码
其他 PasswordEncoder

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

Spring Security 默认利用 DelegatingPasswordEncoder。然而,你可以通过将 PasswordEncoder 袒露为 Spring Bean 来举行定制。
如果你是从 Spring Security 4.2.x 迁移过来的,你可以通过袒露一个 NoOpPasswordEncoder Bean 来恢复到从前的行为。
恢复到 NoOpPasswordEncoder 被认为是 不安全的。你应该转而利用 DelegatingPasswordEncoder来支持安全的暗码编码。
NoOpPasswordEncoder
  1. @Bean
  2. public static NoOpPasswordEncoder passwordEncoder() {
  3.     return NoOpPasswordEncoder.getInstance();
  4. }
复制代码
  1. <b:bean id="passwordEncoder"
  2.     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
  1. http
  2.     .passwordManagement(Customizer.withDefaults())
复制代码
  1. <sec:password-management/>
复制代码
然后,当暗码管理器导航到 /.well-known/change-password 时,Spring Security 将重定向你的端点,/change-password。
或者,如果你的端点是 /change-password 以外的东西,你也可以像这样指定。
Change Password Endpoint
  1. http
  2.     .passwordManagement((management) -> management
  3.         .changePasswordPage("/update-password")
  4.     )
复制代码
  1. <sec:password-management change-password-page="/update-password"/>
复制代码
通过上述配置,当暗码管理器导航到 /.well-known/change-password 时,那么 Spring Security 将重定向到 /update-password。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

麻花痒

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表