shiro入門學習--使用MD5和salt進行加密|練氣後期

賴柄灃發表於2020-10-08

寫在前面

在上一篇文章《Shiro入門學習---使用自定義Realm完成認證|練氣中期》當中,我們學會了使用自定義Realm實現shiro資料來源的切換,我們可以切換成從關聯式資料庫如MySQL中讀取使用者認證資訊進行認證,亦可從非關係型資料庫例如mongodb中讀取使用者認證資訊進行認證。這是一個偉大的進度,這使得我們可以使用shiro來提升我們應用程式的安全度了,

那麼,請大家思考一個問題,我們的應用程式真的安全了嗎?

我把我們麼上一篇文章當中的認證方法程式碼摘抄在下面給大家看看

/**認證
 * @author 賴柄灃 bingfengdev@aliyun.com
 * @date 2020-10-04 11:01:50
 * @param authenticationToken
 * @return org.apache.shiro.authz.AuthorizationInfo
 * @throws AuthenticationException
 * @version 1.0
 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    // 1. 從token中獲取使用者名稱
    String principal = (String) authenticationToken.getPrincipal();

    //2. 根據使用者名稱查詢資料庫並封裝成authenticationinfo物件返回(模擬)
    if (principal == "xiangbei") {
        AuthenticationInfo authInfo = new SimpleAuthenticationInfo("xiangbei","123",this.getName());
        return authInfo;
    }

    return null;
}

在16行當中,我們模擬從資料庫當中查詢出了使用者的註冊資訊,包括賬戶和密碼,並且這裡的密碼是明文的。這意味著如果我們的使用者密碼被洩露了(這裡使用者原因導致的洩露除外),那麼一些不友好的朋友將可以隨意的進出我們的系統。這不但讓我們的應用程式變得不安全,而且還會讓我們面臨法律風險。

以下內容摘自《網路安全法》

第三十四條 網路運營者應當建立健全使用者資訊保護制 度,加強對使用者個人資訊、隱私和商業祕密的保護

第三十五條 網路運營者收集、使用公民個人資訊,應當 遵循合法、正當、必要的原則,明示收集、使用資訊的目 的、方式和範圍,並經被收集者同意。 網路運營者不得收集與其提供的服務無關的公民個人 資訊,不得違反法律、行政法規的規定和雙方的約定收 集、使用公民個人資訊,並應當依照法律、行政法規的規 定或者與使用者的約定,處理其儲存的公民個人資訊。 網路運營者收集、使用公民個人資訊,應當公開其收 集、使用規則。

第三十六條 網路運營者對其收集的公民個人資訊必須嚴 格保密,不得洩露、篡改、毀損,不得出售或者非法向他 人提供。 網路運營者應當採取技術措施和其他必要措施,確保 公民個人資訊保安,防止其收集的公民個人資訊洩露、毀 損、丟失。在發生或者可能發生資訊洩露、毀損、丟失的 情況時,應當立即採取補救措施,告知可能受到影響的用 戶,並按照規定向有關主管部門報告。

所以,我們需要對使用者資訊進行加密保護。對於賬戶密碼資訊,我們應該採取不可逆的加密方式。也就是說,我們對密碼進行加密儲存後,哪怕其獲取了我們的密文,他也不能得到我們的密碼明文。這樣就對我們的使用者資訊起到了一個很好的保護作用。

MD5加密演算法和salt鹽值加密

MD5加密演算法

什麼是MD5加密

MD5資訊摘要演算法(英語:MD5 Message-Digest Algorithm),一種被廣泛使用的密碼雜湊函式,可以產生出一個128位(16位元組)的雜湊值(hash value),用於確保資訊傳輸完整一致。

特點

  1. 不可逆,也就是說其本身上不能由密文推出明文,

    但是,如果明文比較簡單常見,還是存在洩露風險,例如先生成好簡單明文的密文,然後使用窮舉法進行破解;

  2. 對於同一個明文,無論加密多少次其密文都是一樣的;

  3. 生成的結果始終是一個16進位制的32位字串。

作用

  1. 數字簽名(校驗和)

    例如對於一份檔案,為了保證網路傳輸當中不發生改變,我提前對其用md5加密演算法進行加密,得到一段密文。我將這份檔案和密文分別發給你。你在收到檔案後也對其使用md5加密一次,得到一個密文。這時,你就可以比較兩個密文是否一致,如果一致,則檔案沒有被篡改,反之,檔案已經被篡改。

  2. 加密

  3. 垃圾郵件篩選

    原理和作用1一樣

salt鹽值加密策略

在上面的介紹md5加密演算法時我們講到,雖然MD5演算法本身不可逆,但是如果使用者採用簡單的字串作為密碼的話,仍然有被暴力破解的風險。因此,為了解決這個問題,我們需要在對密碼加密之前使其變得複雜化。

而加鹽就是其中的一種方式。所謂的加鹽就是在原密碼的基礎上,加上一段隨機字串。然後再加密。

當然,如果鹽值隨著密碼一起被洩露出去,也是存在著密碼被破解的風險的,我們只能做到相對安全。

為了增加破解難度,可以在加鹽時採取一定的策略,例如雜湊加鹽、加密後多次雜湊。

當然,這要在安全跟效能直接做個平衡。

shiro使用MD5+salt加密

分析

在進行編碼之前,我們需要理一下流程:

  1. 使用者註冊或系統分配賬戶時,服務層在接收到賬號和憑證資訊後,先對憑證資訊採用md5+salt進行加密處理,然後將賬號、加密後的密碼還有鹽值存入資料庫;

  2. 使用者登入請求接收後,先根據請求中的賬號查詢資料庫:

    2.1 如果沒有查到,直接返回“使用者名稱或密碼錯誤”的類似提示

    2.2 如果查到了賬戶資訊,就執行步驟3;

  3. 將賬號和加鹽後憑證封裝成AuthenticationInfo物件返回給shiro,shiro執行步驟4

  4. 對請求中的憑證進行加鹽處理並執行步驟5

  5. 對加鹽後的憑證進行md5加密,並將密文跟資料庫當中的儲存的密文進行比對:

    5.1 如果匹配成功,則認證通過

    5.2 如果匹配失敗,則返回“使用者名稱或密碼錯誤”的類似提示

實現

編寫自定義Realm並切換掉預設的憑證匹配器

/**自定義Realm物件
 * @author 賴柄灃 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/10/4 11:00
 */
public class MySqlRealm extends AuthorizingRealm {

    public MySqlRealm() {
        //設定憑證匹配器,修改為hash憑證匹配器
        HashedCredentialsMatcher myCredentialsMatcher = new HashedCredentialsMatcher();
        //設定演算法
        myCredentialsMatcher.setHashAlgorithmName("md5");
        //雜湊次數
        myCredentialsMatcher.setHashIterations(1024);
        this.setCredentialsMatcher(myCredentialsMatcher);
    }

    /**授權
     * @author 賴柄灃 bingfengdev@aliyun.com
     * @date 2020-10-04 11:01:50
     * @param principalCollection
     * @return org.apache.shiro.authz.AuthorizationInfo
     * @throws AuthenticationException
     * @version 1.0
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        return null;
    }

    /**認證
     * @author 賴柄灃 bingfengdev@aliyun.com
     * @date 2020-10-04 11:01:50
     * @param authenticationToken
     * @return org.apache.shiro.authz.AuthorizationInfo
     * @throws AuthenticationException
     * @version 1.0
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 1. 從token中獲取使用者名稱
        String principal = (String) authenticationToken.getPrincipal();

        //2. 根據使用者名稱查詢資料庫並封裝成authenticationinfo物件返回(模擬)
        if (principal == "xiangbei") {
            //四個引數分別是資料庫中的賬號、加密後的密碼、鹽值、realm名字
            AuthenticationInfo authInfo = new SimpleAuthenticationInfo("xiangbei",
                    "ff595c47b51b4cf70fddce090f68879e",
                    ByteSource.Util.bytes("ee575f62-0dda-44f2-b75e-4efef795018f"),
                    this.getName());
            return authInfo;
        }

        return null;
    }
}

編寫認證器

/**認證管理器
 * @author 賴柄灃 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/10/4 11:11
 */
public class CurrentSystemAuthenticator {
    private DefaultSecurityManager securityManager;
    public CurrentSystemAuthenticator() {
        //建立安全管理器
        securityManager = new DefaultSecurityManager();

        //設定自定義realm
        this.securityManager.setRealm(new MySqlRealm());

        //將安全管理器設定到安全工具類中
        SecurityUtils.setSecurityManager(securityManager);

    }

    public void authenticate(String username,String password) {
        //獲取當前登入主題
        Subject subject = SecurityUtils.getSubject();

        //生成toeken
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        //進行認證
        try {
            subject.login(token);
        }catch (UnknownAccountException | IncorrectCredentialsException e) {
            System.out.println("使用者名稱或密碼不正確");
        }


        //列印認證狀態
        if (subject.isAuthenticated()){
            System.out.println(token.getPrincipal()+" 認證通過!");
        }else {
            System.out.println(token.getPrincipal()+" 認證未通過!");
        }





    }
}

測試

生成加密後的密碼
/**
 * @author 賴柄灃 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/10/4 21:37
 */
public class Md5Test {

    @Test
    public void testMd5(){
        //三個引數分別對應密碼明文、鹽值、雜湊次數
        String salt = UUID.randomUUID().toString();
        Md5Hash md5Hash = new Md5Hash("123", salt,1024);
        System.out.println("密文:"+md5Hash.toHex());
        System.out.println("鹽值:"+salt);
    }
}

輸出

密文:ff595c47b51b4cf70fddce090f68879e
鹽值:ee575f62-0dda-44f2-b75e-4efef795018f
進行認證測試
/**
 * @author 賴柄灃 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/10/4 11:20
 */
public class AuthcTest {
    private CurrentSystemAuthenticator authenticator;
    @Before
    public void init() {
        this.authenticator = new CurrentSystemAuthenticator();
    }

    @Test
    public void testAuthc(){
        this.authenticator.authenticate("xiangbei","123");
    }


}

輸出

xiangbei 認證通過!

寫在最後

在這篇文章當中,我們主要是簡單瞭解了shiro中的加密策略以及如何使用MD5+salt對密碼進行加密。大家可以嘗試著將MD5換成SHA-256加密演算法再測一下。

在下一篇文章當中,作者將介紹SpringBoot整合Shiro的相關內容,文章可能有點長,會考慮分兩次寫。請大家多多關注。

歡迎大家點贊、轉發、分享。轉載註明出處時要帶有原文連結。

相關文章