Spring Security系列三 用戶密碼加密實現

前言

Spring Security系列二 用戶登錄認證數據庫實現中,我們已經把對用戶的認證改成了數據庫實現,功能上雖然完成了,但是用戶的密碼卻都是以明文保存的,這在實際項目中安全係數上會有所欠缺。在本章中我們將實現如何對用戶的密碼進行加密。

Spring Security中的密碼加密

Spring Security中,對密碼的加密都是由PasswordEncoder來完成的。

那什麼時候會調用這個PasswordEncoder呢?這就要回到前面實現數據庫登錄認證時的DaoAuthenticationProvider了。在DaoAuthenticationProvider中,除了UserDetailsService之外還有其它的幾個屬性,其中一個就是PasswordEncoderUserDetailsService前面我們已經實現了,現在要實現PasswordEncoder,密碼加密功能主要就是靠它來完成。

Spring Security中的PasswordEncoder

其實在Spring Security中,已經對PasswordEncoder有了很多實現,包括md5加密、SHA-256加密等等,一般情況下我們只要直接拿來用就可以了。

查看類DaoAuthenticationProvidersetPasswordEncoder方法:

public void setPasswordEncoder(Object passwordEncoder) {
    ...
}

會發現參數類型居然是Object類型,這是因爲在內置的PasswordEncoder中,又分了兩條路線,應該是隨着版本的更新優化而衍生的,但爲了兼容老版本所以兩個都保留了下來,這裏就都分別介紹一下。

老的PasswordEncoder

具體是指接口:org.springframework.security.authentication.encoding.PasswordEncoder

之所以說它老是因爲在該接口上已經標了@Deprecated註解不推薦使用了,但相應的實現類卻沒有標註,所以目前使用上依然是相當廣泛的,很多人可能並不知道已經@Deprecated了。

它的類圖結構如下:

PasswordEncoder類結構圖

可以看到有很多常用的PasswordEncoder已經有實現了,這裏拿最常用的Md5PasswordEncoder來做示例。

想要使用密碼加密就必須指定使用哪個PasswordEncoder,但是在AuthenticationManagerBuilder中並沒有可以快速指定PasswordEncoder的地方,所以這裏必須自己聲明AuthenticationProvider,然後設置UserDetailsServicePasswordEncoder,具體代碼如下:

@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當前推薦使用的接口。

它的類圖如下:

新的PasswordEncoder類圖

實現類只有三個,簡單明瞭,但加密安全性卻提高了。

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類圖

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);
    ...
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章