背景
抱著什么都不懂的情況下去學(xué)習(xí)使用spring boot 2.x集成oauth2功能,所有的參數(shù)后臺(tái)直接明文配置,結(jié)果發(fā)現(xiàn)報(bào)錯(cuò)了,怎么都無法返回正確的token,Full authentication is required to access this resource
通過后臺(tái)日志報(bào)錯(cuò)內(nèi)容是: There is no PasswordEncoder mapped for the id null
尋根
通過日志,找到了報(bào)錯(cuò)的這個(gè)類 UnmappedIdPasswordEncoder#matches(CharSequence rawPassword,String prefixEncodedPassword)
public class DelegatingPasswordEncoder implements PasswordEncoder {
private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();
// 省略代碼
private class UnmappedIdPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
throw new UnsupportedOperationException("encode is not supported");
}
@Override
public boolean matches(CharSequence rawPassword,
String prefixEncodedPassword) {
String id = extractId(prefixEncodedPassword);
throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\"");
}
}
}
這個(gè)類是DelegatingPasswordEncoder的一個(gè)內(nèi)部類,通過查看代碼發(fā)現(xiàn)有個(gè)屬性叫做defaultPasswordEncoderForMatches說明這個(gè)內(nèi)部類是一個(gè)默認(rèn)的密碼加密方式,但是從matches這個(gè)方法可以看出目標(biāo)是獲取{}一對(duì)大括號(hào)之間的字符串,如果找不到就會(huì)報(bào)錯(cuò),這說明所有的密碼的格式必然是要{xxx}xxxxx這種方式。
// 獲取 {} 之間的字符串
private String extractId(String prefixEncodedPassword) {
if (prefixEncodedPassword == null) {
return null;
}
int start = prefixEncodedPassword.indexOf(PREFIX);
if (start != 0) {
return null;
}
int end = prefixEncodedPassword.indexOf(SUFFIX, start);
if (end < 0) {
return null;
}
return prefixEncodedPassword.substring(start + 1, end);
}
繼續(xù)從這個(gè)內(nèi)部類找,可以看到調(diào)用內(nèi)部類的方法位于
// 這個(gè)matches方式就是進(jìn)行密碼校驗(yàn)的
@Override
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
// 從字段名字很容易理解
// rawPassword 原始密碼:也就是用戶請(qǐng)求的密碼
// prefixEncodedPassword 帶有前綴的加密算法加密之后的密碼
if (rawPassword == null && prefixEncodedPassword == null) {
return true;
}
// 獲取密碼前綴加密算法
String id = extractId(prefixEncodedPassword);
// 從一個(gè)加密算法的map種獲取加密具體的加密算法對(duì)象
PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
if (delegate == null) {
// 如果獲取不到就會(huì)使用默認(rèn)的密碼加密算法,實(shí)際是一定會(huì)拋出錯(cuò)誤的
// 這個(gè)內(nèi)部類里面再次調(diào)用extraId的目的只是為了獲取一下錯(cuò)誤的前綴而已
return this.defaultPasswordEncoderForMatches
.matches(rawPassword, prefixEncodedPassword);
}
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
return delegate.matches(rawPassword, encodedPassword);
}
通過以上代碼可以看到具體的加密算法位于this.idToPasswordEncoder,這是一個(gè)map,通過DelegatingPasswordEncoder的構(gòu)造方法進(jìn)行初始化的,查看創(chuàng)建該構(gòu)造函數(shù)追蹤到PasswordEncoderFactories,只有一個(gè)方法
// 這個(gè)初始化就顯示了所有oauth2的加密方式,比如常用的MD5,bcrypt等,
// ==noop==代表不加密
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
至此oauth對(duì)密碼的處理,以后就能夠很熟悉了
存儲(chǔ)的密碼格式就是 {xxx}yyyyyyyy 的方式了
比如:123456 -> 存儲(chǔ)為: {MD5}e10adc3949ba59abbe56e057f20f883e
123456 -> 存儲(chǔ)為:{bcrypt}10$cDzOYM.AnjxRKyAwQ8LYR.4tJ3WlQKrC4oeus0NfqQsQfjG0jBiRG
在調(diào)試跟進(jìn)源碼的過程中,發(fā)現(xiàn)oauth2的clientSecret 以及密碼模式的password都是走的同一個(gè)邏輯校驗(yàn),這也讓我更容易理解客戶端/自然人都可以作為資源所有者這個(gè)概念。