導讀
在項目開發中,越來越重視安全相關的功能。在使用Spring Boot進行項目開發的時候,使用Spring Security框架是一個不錯的選擇。
開發登錄認證功能的時候,一般情況都不會將原始密碼明文存儲到數據庫中,那么就需要對密碼進行加密,Spring Security推薦使用的是BCryptPasswordEncoder,說明它有可取之處。
問題
問題:在登錄認證的時候,每次均需耗費5S以上的時間,這是用戶無法忍受的事情。
排查過程:通過visualVM 的線程Dump的信息發現,在自定義的認證過濾器中的attemptAuthentication()方法進入認證之后,在等待驗證密碼成功返回的時間最長。將目標放在密碼比對的方法上,由於注入的是BCryptPasswordEncoder,找到它調用的比對方法matches(),傳入原始密碼和數據庫中的密碼,返回是否匹配的布爾值。
既然發現目標了,那就去扣一扣它的源碼吧。
BCryptPasswordEncoder.class
/**
* Implementation of PasswordEncoder that uses the BCrypt strong hashing function. Clients
* can optionally supply a "version" ($2a, $2b, $2y) and a "strength" (a.k.a. log rounds
* in BCrypt) and a SecureRandom instance. The larger the strength parameter the more work
* will have to be done (exponentially) to hash the passwords. The default value is 10.
*
* @author Dave Syer
*/
private Pattern BCRYPT_PATTERN = Pattern.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}");
private final int strength;
private final BCryptVersion version;
private final SecureRandom random;
通過注釋,不難發現,生成的密碼字符串有一定的規律,構造方法里面可選參數有三個,strength,version,random
參數strength:默認值為10,可選值為4-31;
參數version:默認值為$2a,可選值為$2a, $2b, $2y;
參數random:SecureRandom的實例,默認值為空;
該類中生成密碼的方法encode()需要傳入原始密碼字符串,調用算法實現類BCrypt的hashpw()方法,增加一個salt參數,該參數的值由BCrypt的gensalt()結合strength, version, random生成。
進行密碼比較,也就是調用matches()方法的時候,首先需要將用戶上傳的密碼(原始密碼)進行加密,因此可以得出耗時的操作在於將原始密碼加密。
當需要加密的密碼相同時,可能影響性能的因素就只有strength, version, random,使用控制變量法來進行測試,但是參數random沒有太大測試的意義。
1.改變參數strength的值,保持其余兩個值為默認值。
// strength設置為10,也就是默認值時
String password = new BCryptPasswordEncoder(10).encode("password");
System.out.println(password);

// strength設置為16的時候,不要問我為什么測試16,問就是我當時手抽設置的就是16
String password = new BCryptPasswordEncoder(16).encode("password");
System.out.println(password);

// strength設置為20的時候
String password = new BCryptPasswordEncoder(20).encode("password");
System.out.println(password);

由於strength數值越大,耗時越嚴重,后面的值不測試了(因為測試了25,運行了19分鍾都沒有出來)。
2.改變參數version的值,保持其余兩個值為默認值。
參數version設置為$2a,由於是默認值,故同測試1中的第二個用例。
// versionh設置為$2b
String password = new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2B, 16).encode("password");
System.out.println(password);

// version設置為$2y
String password = new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2Y, 16).encode("password");
System.out.println(password);

總結
通過以上測試可以看出,參數strength的值改變對於生成加密密碼的時間影響是最大的。
為甚麽記錄這篇隨筆?希望對遇到同樣問題的你有所幫助。
PS:研究到這里就結束了,保住頭發要緊。菜鳥開發第一次寫博客,請各位看官輕噴~
