spring cloud OAuth2的用户认证安全措施
1、用户登录认证时,如用户不存在,仍然检查密码,以防止黑客嗅探用户是否存在(SEC-2056)。
相关源码:
org.springframework.security.authentication.dao.DaoAuthenticationProvider#retrieveUser
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;
try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
catch (UsernameNotFoundException notFound) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
presentedPassword, null);
}
throw notFound;
}
}
2、比较密码是否匹配时,采用时间恒等的算法
如果服务端密码是使用哈希值进行存储的,在登录时使用固定的时间来比较哈希值可以防止黑客对系统使用基于时间差的攻击,以此获取密码的哈希值,然后进行本地破解。
在Java中,通常我们比较两个字节序列(字符串)是否相同的做法是使用String的equals()方法,即从第一个字节开始,每个字节逐一顺序比较。只要发现某个字节不同,就可以知道它们是不同的,立即返回false。如果遍历整个字符串没有找到不同的字节,可以确认两个字符串就是相同的,可以返回true。这意味着比较两个字符串,如果它们相同的长度不一样,花费的时间不一样。开始部分相同的长度越长,花费的时间也就越长。
例如,字符串 “ABCD” 和 “123456” 的equals比较,会立即看到,第一个字符是不同的,就不需要检查字符串的其余部分。相反,当字符串 “11111111111a” 和 “11111111111b” 进行比较时,比较算法就需要遍历最后一位前所有的 “1” ,然后才能知道它们是不同的。
假设黑客试图入侵一个在线系统,这个系统限制了每秒只能尝试一次用户认证,还假设攻击者已经知道系统对密码进行哈希的所有参数(salt、哈希函数等),但是不知道密码存储在数据库的哈希值和密码明文。如果黑客能精确测量在线系统比较猜测的密码和真实密码的实际耗时,那么他就能使用时序攻击获得密码的哈希值,然后再进行离线破解,从而绕过系统对认证频率的限制。
首先黑客准备256个字符串,它们的哈希值的第一字节包含了所有可能的情况,然后将每个字符串发送给在线系统尝试登陆,并记录系统响应所消耗的时间。耗时最长的字符串就是第一字节相匹配的。攻击者知道第一字节后,并可以用同样的方式继续猜测第二字节、第三字节等等。一旦黑客获得足够长的哈希值片段,他就可以在自己的机器上来破解,不受在线系统的限制。
如果你用了spring cloud 自带的BCryptPasswordEncoder,相关源码如下,如果你实现自定义的密码验证,也请保证使用时间恒等的安全算法。
相关源码:
org.springframework.security.crypto.bcrypt.BCrypt#equalsNoEarlyReturn
static boolean equalsNoEarlyReturn(String a, String b) {
char[] caa = a.toCharArray();
char[] cab = b.toCharArray();
if (caa.length != cab.length) {
return false;
}
byte ret = 0;
for (int i = 0; i < caa.length; i++) {
ret |= caa[i] ^ cab[i];
}
return ret == 0;
}