背景
抱着什么都不懂的情况下去学习使用spring boot 2.x集成oauth2功能,所有的参数后台直接明文配置,结果发现报错了,怎么都无法返回正确的token,Full authentication is required to access this resource
通过后台日志报错内容是: There is no PasswordEncoder mapped for the id null
寻根
通过日志,找到了报错的这个类 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 + "\"");
}
}
}
这个类是DelegatingPasswordEncoder的一个内部类,通过查看代码发现有个属性叫做defaultPasswordEncoderForMatches说明这个内部类是一个默认的密码加密方式,但是从matches这个方法可以看出目标是获取{}一对大括号之间的字符串,如果找不到就会报错,这说明所有的密码的格式必然是要{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);
}
继续从这个内部类找,可以看到调用内部类的方法位于
// 这个matches方式就是进行密码校验的
@Override
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
// 从字段名字很容易理解
// rawPassword 原始密码:也就是用户请求的密码
// prefixEncodedPassword 带有前缀的加密算法加密之后的密码
if (rawPassword == null && prefixEncodedPassword == null) {
return true;
}
// 获取密码前缀加密算法
String id = extractId(prefixEncodedPassword);
// 从一个加密算法的map种获取加密具体的加密算法对象
PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
if (delegate == null) {
// 如果获取不到就会使用默认的密码加密算法,实际是一定会抛出错误的
// 这个内部类里面再次调用extraId的目的只是为了获取一下错误的前缀而已
return this.defaultPasswordEncoderForMatches
.matches(rawPassword, prefixEncodedPassword);
}
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
return delegate.matches(rawPassword, encodedPassword);
}
通过以上代码可以看到具体的加密算法位于this.idToPasswordEncoder,这是一个map,通过DelegatingPasswordEncoder的构造方法进行初始化的,查看创建该构造函数追踪到PasswordEncoderFactories,只有一个方法
// 这个初始化就显示了所有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对密码的处理,以后就能够很熟悉了
存储的密码格式就是 {xxx}yyyyyyyy 的方式了
比如:123456 -> 存储为: {MD5}e10adc3949ba59abbe56e057f20f883e
123456 -> 存储为:{bcrypt}10$cDzOYM.AnjxRKyAwQ8LYR.4tJ3WlQKrC4oeus0NfqQsQfjG0jBiRG
在调试跟进源码的过程中,发现oauth2的clientSecret 以及密码模式的password都是走的同一个逻辑校验,这也让我更容易理解客户端/自然人都可以作为资源所有者这个概念。
网友评论