明文保存密碼是不可取的褂萧,可以使用 SHA,BCrypt 等對密碼進(jìn)行加密虐秦。
BCrypt 算法與 MD5/SHA 算法有一個很大的區(qū)別创坞,每次生成的 hash 值都是不同的碗短,就可以免除存儲 salt,暴力破解起來也更困難题涨。BCrypt 加密后的字符長度比較長偎谁,有60位,所以用戶表中密碼字段的長度纲堵,如果打算采用 BCrypt 加密存儲巡雨,字段長度不得低于 68(需要前綴 {bcrypt})。
下面的代碼展示怎么使用 BCrypt 進(jìn)行加密:
import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
public class EncryptPassword {
@Test
public void encrypt() {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
for (int i = 0; i < 5; ++i) {
// 每次生成的密碼都不一樣
String encryptedPassword = passwordEncoder.encode("Passw0rd");
System.out.println(encryptedPassword);
System.out.println(passwordEncoder.matches("Passw0rd", encryptedPassword)); // true
System.out.println(passwordEncoder.matches("Password", encryptedPassword)); // false
}
}
}
輸出:
$2a$10$l7vPVeqwb9GiVjURV5J2QO1CM5qxwk00/Ra5qEog0WgP7O5XV0Ble
true
false
$2a$10$jeyMfHF88mNJb9v.mQ7YiuZ8oTU.pHaiKdT1NLOM38eXj7heHZHg2
true
false
$2a$10$ux43/3JcHUC1hszyoJaH0eQhv7LkIVfL7p1cW80WxfxeTr2dUY6kO
true
false
$2a$10$KdUmhaJOJ30klEcKiYT25.fIRPrMs4xONHOQh4JvmpKSjJ8d9.QKG
true
false
$2a$10$gQKUOoFuevnCkoej3.AvAO9YzHKCKYmKuiSfEGHL22piY2FfNDQYu
true
false
隨意取其中任意一個都可以席函,因為每次生成都是不一樣的铐望,所以取第一個就可以了。
Spring Security 使用 BCrypt 加密
Spring Security 4 的時候需要配置 <password-encoder hash="bcrypt"/> 指定加密方式茂附,Spring Security 5 不需要配置了正蛙,而是用戶密碼加前綴的方式表明加密方式,例如
{bcrypt}$2a$10$gQKUOoFuevnCkoej3.AvAO9YzHKCKYmKuiSfEGHL22piY2FfNDQYu 說明是使用 BCrypt 進(jìn)行加密的
{noop}Passw0rd 則是使用明文保存的密碼 (noop: No Operation)
這樣的好處是同一個系統(tǒng)可以使用多種加密方式何之,遷移用戶到新系統(tǒng)時比較就省事了跟畅。Spring Security 5 默認(rèn)支持的密碼加密方式在 PasswordEncoderFactories 中定義:
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new LdapShaPasswordEncoder());
encoders.put("MD4", new Md4PasswordEncoder());
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new StandardPasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
UserService
從數(shù)據(jù)源取得的密碼是加密后的密碼
public class UserService {
private static Map<String, User> users = new HashMap<String, User>();
static {
// 模擬數(shù)據(jù)源,可以是多種溶推,如數(shù)據(jù)庫,LDAP奸攻,從配置文件讀取等
users.put("admin", new User("admin", "{noop}Passw0rd", "ROLE_ADMIN")); // 密碼是 Passw0rd
users.put("alice", new User("alice", "{bcrypt}$2a$10$dtA5fPvVJEBHLPp7FZci9uKJL90zF8T1EQZzP9qownQlf130bdBZW", "ROLE_USER")); // 密碼是 Passw0rd
}
public User findUserByUsername(String username) {
return users.get(username);
}
}
測試
訪問 http://localhost:8080/admin
輸入錯誤的用戶名或密碼蒜危,觀察登陸失敗的頁面
輸入正確的用戶名和密碼,繼續(xù)登陸