前言
Spring Security系列二 用戶登錄認證數據庫實現中,我們已經把對用戶的認證改成了數據庫實現,功能上雖然完成了,但是用戶的密碼卻都是以明文保存的,這在實際項目中安全係數上會有所欠缺。在本章中我們將實現如何對用戶的密碼進行加密。
Spring Security中的密碼加密
在Spring Security
中,對密碼的加密都是由PasswordEncoder
來完成的。
那什麼時候會調用這個PasswordEncoder
呢?這就要回到前面實現數據庫登錄認證時的DaoAuthenticationProvider
了。在DaoAuthenticationProvider
中,除了UserDetailsService
之外還有其它的幾個屬性,其中一個就是PasswordEncoder
,UserDetailsService
前面我們已經實現了,現在要實現PasswordEncoder
,密碼加密功能主要就是靠它來完成。
Spring Security中的PasswordEncoder
其實在Spring Security
中,已經對PasswordEncoder
有了很多實現,包括md5
加密、SHA-256
加密等等,一般情況下我們只要直接拿來用就可以了。
查看類DaoAuthenticationProvider
的setPasswordEncoder
方法:
public void setPasswordEncoder(Object passwordEncoder) {
...
}
會發現參數類型居然是Object
類型,這是因爲在內置的PasswordEncoder
中,又分了兩條路線,應該是隨着版本的更新優化而衍生的,但爲了兼容老版本所以兩個都保留了下來,這裏就都分別介紹一下。
老的PasswordEncoder
具體是指接口:org.springframework.security.authentication.encoding.PasswordEncoder
。
之所以說它老是因爲在該接口上已經標了@Deprecated
註解不推薦使用了,但相應的實現類卻沒有標註,所以目前使用上依然是相當廣泛的,很多人可能並不知道已經@Deprecated
了。
它的類圖結構如下:
可以看到有很多常用的PasswordEncoder
已經有實現了,這裏拿最常用的Md5PasswordEncoder
來做示例。
想要使用密碼加密就必須指定使用哪個PasswordEncoder
,但是在AuthenticationManagerBuilder
中並沒有可以快速指定PasswordEncoder
的地方,所以這裏必須自己聲明AuthenticationProvider
,然後設置UserDetailsService
和PasswordEncoder
,具體代碼如下:
@Bean
public UserDetailsService userDetailsService() {
return new CustomUserDetailsService();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new Md5PasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.userDetailsService(userDetailsService());
auth.authenticationProvider(authenticationProvider());
}
需要注意在configure
中設置了AuthenticationProvider
就要把原先的auth.userDetailsService(userDetailsService())
去掉,不然就會有兩個userDetailsService
的調用和認證,結果必然是一次正確一次不正確,返回你預期之外的結果。
新的PasswordEncoder
具體是指接口:org.springframework.security.crypto.password.PasswordEncoder
,這是spring當前推薦使用的接口。
它的類圖如下:
實現類只有三個,簡單明瞭,但加密安全性卻提高了。
NoOpPasswordEncoder
不多說了,啥也不做按原文本處理,相當於不加密。
StandardPasswordEncoder
1024次迭代的SHA-256
散列哈希加密實現,並使用一個隨機8字節的salt。
BCryptPasswordEncoder
使用BCrypt的強散列哈希加密實現,並可以由客戶端指定加密的強度strength
,強度越高安全性自然就越高,默認爲10.
在Spring
的註釋中,明確寫明瞭如果是開發一個新的項目,BCryptPasswordEncoder
是較好的選擇。
* If you are developing a new system,
* {@link org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder} is a better
* choice both in terms of security and interoperability with other languages.
代碼示例:
@Bean
public org.springframework.security.crypto.password.PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.userDetailsService(userDetailsService());
auth.authenticationProvider(authenticationProvider());
}
給密碼加點鹽 salt
不得不說salt
這名字取的很貼切。
在很多時候我們可能需要給密碼加點指定的前綴或後綴,以防止像123456
這類簡單的密碼被反向破解,這時候就會用到SaltSource
了。
其實SaltSource
隨着PasswordEncoder
的更換目前已是不推薦使用了,但是有必要了解一下它,以及它背後的目的是什麼,以實現更好的密碼安全性。
SaltSource
類圖如下:
SaltSource
的目的就是混淆一下密碼然後再進行加密,防止加密後的字符串被反向破解。像ReflectionSaltSource
可以指定對象的某個屬性值添加到密碼中以增加安全性。
這裏爲簡單起見,我們自己實現一個SaltSource
,在密碼中加固定的字母abc
:
/**
* Created by liyd on 16/11/26.
*/
public class CustomSaltSource implements SaltSource {
@Override
public Object getSalt(UserDetails userDetails) {
return "abc";
}
}
然後指定使用CustomSaltSource
:
@Bean
public SaltSource saltSource() {
return new CustomSaltSource();
}
@Bean
public AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
authenticationProvider.setSaltSource(saltSource());
return authenticationProvider;
}
啓動項目,打上斷點調試,發現在我們的密碼123456
後面加上了abc
:
自然加密後的字符串也相應變了,加強了密碼的安全性。
需要注意SaltSource
只針對老的PasswordEncoder
而言,新的PasswordEncoder
已經不需要使用SaltSource
來加強密碼的安全性了,因爲它的強度可以由用戶指定,強度不同加密後的字符串自然也不同,安全性已經足夠了,就算你想加也會拋出下面的異常:
java.lang.IllegalArgumentException: Salt value must be null when used with crypto module PasswordEncoder
密碼的保存
以上說了密碼的加密校驗,有個前提當然是你在保存數據的時候密碼加密方式得和這個保持一致,這個也不用自己實現,既然已經有了直接把PasswordEncoder
拿來用就行:
@Bean
public AuthenticationProvider authenticationProvider() {
//這裏只做如何使用passwordEncoder與校驗保持一致示例 密碼輸出
String password = passwordEncoder().encode("123456");
System.out.println(password);
...
}