本文参考自Spring Security 5.0.4.RELEASE 的官方文档,团结源码先容了 DelegatingPasswordEncoder,对其事情进程举办阐明并办理个中碰着的问题。包罗 There is no PasswordEncoder mapped for the id “null” 犯科参数异常的正确处理惩罚要领。
PasswordEncoder
首先要领略 DelegatingPasswordEncoder 的浸染和存在意义,昆山软件开发,大白官方为什么要利用它来代替原先的 NoOpPasswordEncoder。
DelegatingPasswordEncoder 和 NoOpPasswordEncoder 都是 PasswordEncoder 接口的实现类。按照官方的界说,Spring Security 的 PasswordEncoder 接口用于执行暗码的单向转换,以便安详地存储暗码。
关于暗码存储的演变汗青这里我不多做先容,简朴来说就是此刻数据库存储的暗码根基都是颠末编码的,而抉择如何编码以及判定未编码的字符序列和编码后的字符串是否匹配就是 PassswordEncoder 的责任。
这里我们可以看一下 PasswordEncoder 接口的源码:
public interface PasswordEncoder { /** * Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or * greater hash combined with an 8-byte or greater randomly generated salt. */ String encode(CharSequence rawPassword); /** * Verify the encoded password obtained from storage matches the submitted raw * password after it too is encoded. Returns true if the passwords match, false if * they do not. The stored password itself is never decoded. * * @param rawPassword the raw password to encode and match * @param encodedPassword the encoded password from storage to compare with * @return true if the raw password, after encoding, matches the encoded password from * storage */ boolean matches(CharSequence rawPassword, String encodedPassword); }
按照源码,我们可以直观地看到 PassswordEncoder 接口只有两个要领,一个是 String encode(CharSequence rawPassword),用于将字符序列(即原暗码)举办编码;另一个要领是 boolean matches(CharSequence rawPassword, String encodedPassword),用于较量字符序列和编码后的暗码是否匹配。
领略了 PasswordEncoder 的浸染后我们来 Spring Security 5.0 之前默认 PasswordEncoder 实现类 NoOpPasswordEncoder。这个类因为不安详已经被标志为过期了。下面就让我们来看看它是如何地不安详的:
1 NoOpPasswordEncoder
事实上,NoOpPasswordEncoder 就是没有编码的编码器,源码如下:
@Deprecated public final class NoOpPasswordEncoder implements PasswordEncoder { public String encode(CharSequence rawPassword) { return rawPassword.toString(); } public boolean matches(CharSequence rawPassword, String encodedPassword) { return rawPassword.toString().equals(encodedPassword); } /** * Get the singleton {@link NoOpPasswordEncoder}. */ public static PasswordEncoder getInstance() { return INSTANCE; } private static final PasswordEncoder INSTANCE = new NoOpPasswordEncoder(); private NoOpPasswordEncoder() { } }
可以看到,NoOpPasswordEncoder 的 encode 要领就只是简朴地把字符序列转成字符串。也就是说,你输入的暗码 ”123456” 存储在数据库里仍然是 ”123456”,这样假如数据库被攻破的话暗码就直接泄露了,十分不安详。并且 NoOpPasswordEncoder 也就失去了所谓暗码编码器的意义了。
不外正因其十分简朴,在 Spring Security 5.0 之前 NoOpPasswordEncoder 是作为默认的暗码编码器而存在到,它可以是你没有主动加密时的一个默认选择。
别的,NoOpPasswordEncoder 的实现是一个尺度的饿汉单例模式,关于单例模式可以看这一篇文章:单例模式及其4种推荐写法和3类掩护手段。
2 DelegatingPasswordEncoder
通过上面的进修我们可以知道,跟着安详要求的提高之前的默认暗码编码器 NoOpPasswordEncoder 已经被 “不推荐”了,那我们有来由猜测此刻的默认暗码编码器换成了利用某一特定算法的编码器。但是这样便会带来三个问题:
简朴来说,就是新的暗码编码器和旧暗码的兼容性、自身的稳健性以及需要必然的可变性(切换到更好的算法)。听起来是不是十分抵牾?那我们就来看看 DelegatingPasswordEncoder 是怎么办理这个问题的。在看办理要领之前先看利用 DelegatingPasswordEncoder 能到达的结果:
下面我们来看看 DelegatingPasswordEncoder 的结构要领
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) { if(idForEncode == null) { throw new IllegalArgumentException("idForEncode cannot be null"); } if(!idToPasswordEncoder.containsKey(idForEncode)) { throw new IllegalArgumentException("idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder); } for(String id : idToPasswordEncoder.keySet()) { if(id == null) { continue; } if(id.contains(PREFIX)) { throw new IllegalArgumentException("id " + id + " cannot contain " + PREFIX); } if(id.contains(SUFFIX)) { throw new IllegalArgumentException("id " + id + " cannot contain " + SUFFIX); } } this.idForEncode = idForEncode; this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode); this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder); }