Spring BcryptPasswordEncorder Log Rounds引數說明

肖老闆發表於2017-09-05

Spring BcryptPasswordEncorder log rounds引數說明

今天在做使用者上傳Excel表格匯入資料到Mongodb資料庫的時候遇到一個超時的問題,比較有意思,在這裡記錄一下!需求是這樣的,使用者通過頁面選擇本地的Excel表格,通過介面將Excel表格上傳到後臺,由後端解析Excel表格中的資料,解析成功後儲存到資料庫中。對於Excel表格的處理我表示輕車熟路,本來這個功能已經做好了,而且之前還測試過上傳有上萬條記錄的Excel表格的匯入,完全沒有壓力。但是今天在匯入新使用者的時候突然提示500超時了!以下是我的解決過程:
1. 首先想到的時候Spring MVC的介面預設超時時間設定得太短了,Spring的文件說如果不設定預設超時時間,那麼會根據伺服器的超時時間進行設定,Tomcat一般是10s。然後我就修改了Spring的配置,增加了如下兩處超時時間配置:

server:
  connection-timeout: 60000

spring:
  mvc:
    async:
      request-timeout: 60000

再次上傳該使用者表,WTF,還是超時。

  1. 接著考慮是不是前端訪問介面的時候有設定預設的超時時間,仔細F12看呼叫過程,否定了這個想法。
  2. 然後只能單步除錯了,但是類似這樣的問題設定斷點進行除錯往往會影響程式執行,不易復現問題,所以我選擇分步列印時間戳的辦法。很順利,發現了出現問題的程式碼塊,如下:
//使用者賬號資訊批量初始化設定
users.stream().forEach(tempStudent -> {
    //設定預設使用者角色
    tempUser.setRoles(Arrays.asList(DefaultRole));
    //設定使用者預設登入名為編號
    tempUser.setUsername(tempUser.getCode());
    //設定使用者預設密碼為編號
    String md5Hex = DigestUtils.md5Hex(tempUser.getCode());
    tempUser.setPassword(md5Hex);
});

再次使用逐行註釋法,最終定位到出現問題的程式碼是:

tempUser.setPassword(md5Hex);

而我在該程式碼塊裡面做的工作如下:

public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder(4);
public void setPassword(String password) {
    this.password = PASSWORD_ENCODER.encode(password);
}

設定使用者密碼時使用Spring框架的BCryptPasswordEncoder對密碼進行了加密。發現這個問題時感覺很困惑,按理說Spring框架這麼成熟,不會有這麼明細的效能瓶頸啊!再仔細去看裡面的建構函式和加密方法,大概發現了問題所在,關鍵程式碼如下:

public BCryptPasswordEncoder() {
    this(-1);
}
public String encode(CharSequence rawPassword) {
    String salt;
    if (strength > 0) {
        if (random != null) {
            salt = BCrypt.gensalt(strength, random);
        }
        else {
            salt = BCrypt.gensalt(strength);
        }
    }
    else {
        salt = BCrypt.gensalt();
    }
    return BCrypt.hashpw(rawPassword.toString(), salt);
}
public static String gensalt() {
    return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
}
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;

原來是如果構造BCryptPasswordEncoder時不傳引數就預設使用長度為10的LOG ROUND,我試著傳入最小的構造引數4之後,一下子就處理完了,500錯誤也沒有了。搜尋了相關的關鍵詞,LOG ROUND這個引數大概的意思是做加密時重複處理的輪數,而且其複雜度是指數級遞增,也就是傳4的時候是24次方,預設傳輸為10的時候是210次方!難怪其速度會差距這麼大!

最後呢,顯然這種在介面裡面同步處理大批量資料的方法是不可取的,應該使用ActiveMQ之類的協議進行非同步處理。嗯,功能完善後再迭代吧!

相關文章