Spring Security是一个框架,它提供了认证、授权和对常见攻击的保护。它对保护命令式和反应式应用程序有一流的支持,是保护基于Spring的应用程序的事实标准。
Features
Authentication
Spring Security提供了对认证的全面支持。认证是指我们如何验证试图访问特定资源的人的身份。一个常见的验证用户的方法是要求用户输入用户名和密码。一旦进行了认证,我们就知道了身份并可以执行授权。
Password Storage
Spring Security的PasswordEncoder接口用于对密码进行单向转换,以使密码得到安全的存储。鉴于PasswordEncoder是一个单向的转换,当密码转换需要双向的时候,它就不适用了(即存储用于验证数据库的凭证)。通常,PasswordEncoder用于存储密码,在认证时需要与用户提供的密码进行比较。
在Spring Security 5.0之前,默认的PasswordEncoder是NoOpPasswordEncoder,需要纯文本密码。根据密码历史部分,你可能会认为现在的默认密码编码器是类似BCryptPasswordEncoder的东西。然而,这忽略了三个现实的问题:
- 有许多应用程序使用旧的密码编码,不能轻易迁移
- 密码存储的最佳实践将再次改变
- 作为一个框架,Spring Security不能频繁地进行突破性的改变。
相反,Spring Security引入了DelegatingPasswordEncoder,它通过以下方式解决了所有的问题:
- 确保使用当前的密码存储建议对密码进行编码
- 允许验证现代和传统格式的密码
- 允许在未来升级编码
Password Encoding
```java 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);
> 传入构造函数的idForEncode决定了哪一个PasswordEncoder将被用于编码密码。在我们上面构造的DelegatingPasswordEncoder中,这意味着编码密码的结果将被委托给BCryptPasswordEncoder,并以{bcrypt}为前缀。最终的结果会是这样的。
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
<a name="t7ozM"></a>
#### Password Matching
> 匹配是根据{id}和构造函数中提供的id到PasswordEncoder的映射来完成的。我们在密码存储格式中的例子提供了一个如何完成的工作实例。默认情况下,用一个密码和一个没有被映射的id(包括一个空id)调用matches(CharSequence, String)的结果将导致IllegalArgumentException。这个行为可以使用DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)来定制。
>
> 通过使用id,我们可以在任何密码编码上进行匹配,但使用最现代的密码编码对密码进行编码。这一点很重要,因为与加密不同,密码散列的设计使我们没有简单的方法来恢复明文。由于没有办法恢复明文,这使得密码的迁移变得很困难。虽然用户迁移NoOpPasswordEncoder很简单,但我们选择默认包含它,以使入门体验简单。
```java
User user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("user")
.build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
Troubleshooting
当存储的密码之一没有密码存储格式中描述的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。解决这个问题的最简单的方法是弄清楚你的密码目前是如何被存储的,并明确提供正确的PasswordEncoder。
如果你是从Spring Security 4.2.x迁移过来的,你可以通过暴露一个NoOpPasswordEncoder Bean来恢复到以前的行为。
另外,你可以在所有的密码前加上正确的ID,并继续使用DelegatingPasswordEncoder。例如,如果你使用的是BCrypt,你可以将你的密码从类似的地方迁移过来:
$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
to
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
BCryptPasswordEncoder
BCryptPasswordEncoder的实现使用广泛支持的bcrypt算法对密码进行散列。为了使它对密码破解有更强的抵抗力,bcrypt故意做得很慢。像其他自适应单向函数一样,它应该被调整为在你的系统上验证一个密码需要1秒左右。BCryptPasswordEncoder的默认实现使用强度10,这在BCryptPasswordEncoder的Javadoc中提到。我们鼓励你在自己的系统上调整和测试强度参数,使其大约需要1秒钟来验证一个密码。
// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
Password Storage Configuration
Spring Security默认使用DelegatingPasswordEncoder。然而,这可以通过将PasswordEncoder暴露为Spring Bean来定制。 如果你是从Spring Security 4.2.x迁移过来的,你可以通过暴露一个NoOpPasswordEncoder Bean来恢复到以前的行为。
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
:::warning 恢复到NoOpPasswordEncoder被认为是不安全的。你应该转而使用DelegatingPasswordEncoder来支持安全的密码编码。 :::
Authorization
Spring Security提供了对授权的全面支持。授权是确定谁被允许访问一个特定的资源。Spring Security通过允许基于请求的授权和基于方法的授权来提供深度防御。