Shiro 認證流程詳解


1. 認證

  身份認證,就是判斷一個用戶是否為合法用戶的處理過程。最常用的簡單身份認證方式是系統通過核對用戶輸入的用戶名口令,看其是否與系統中存儲的該用戶的用戶名和口令一致,來判斷用戶身份是否正確。

2. Shiro 中認證的關鍵對象

  • Subject 主體:訪問系統的用戶,主體可以是用戶、程序等等,進行認證的都稱為主體;
  • Principal 身份信息:是主體(Subject)進行身份認證的標識,標識必須具有唯一性,如用戶名、手機號、郵箱地址等,一個主體可以有多個身份,但是必須有一個主身份(Primary Principal)
  • Credential 憑證信息:是只有主體自己知道的安全信息,如密碼、證書等。

3. 認證流程

         

4. 認證開發

4.1  模擬數據庫中已存儲用戶信息【創建 shiro.ini 文件】

[users]
wangwu=123
zhangsan=123456
lisi=789

4.2 開發認證代碼

public class TestAuthenticator {

    public static void main(String[] args) {
        // 1. 創建安全管理器
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 2. 給安全管理器設置 realm 【Realm 需要獲取用戶認證的數據源信息】
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        // 3. SecurityUtils 設置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        // 4. 獲取關鍵對象 Subject 主體
        Subject subject = SecurityUtils.getSubject();
        // 5. 創建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("wangwu", "123");
        // 用戶認證
        try {
            System.out.println("認證狀態: " + subject.isAuthenticated());
            subject.login(token);
            System.out.println("認證狀態: " + subject.isAuthenticated());
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("認證失敗: 用戶名不存在~");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("認證失敗: 密碼錯誤~");
        }
    }
}

 

  • DisabledAccountException(帳號被禁用)

  • LockedAccountException(帳號被鎖定)

  • ExcessiveAttemptsException(登錄失敗次數過多)

  • ExpiredCredentialsException(憑證過期)

4.3 自定義 Realm

  上邊的程序使用的是 Shiro 自帶的 IniRealm,IniRealm 從 shiro.ini 配置文件中讀取用戶的信息,大部分情況下需要從系統的數據庫中讀取用戶信息,所以需要自定義 Realm。

1)Shiro 自定義 Realm 

         

2)根據認證源碼使用的是 SimpleAccountRealm 【只保留認證和授權這兩部分代碼】

public class SimpleAccountRealm extends AuthorizingRealm {
        //.......省略
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        SimpleAccount account = getUser(upToken.getUsername());

        if (account != null) {

            if (account.isLocked()) {
                throw new LockedAccountException("Account [" + account + "] is locked.");
            }
            if (account.isCredentialsExpired()) {
                String msg = "The credentials for account [" + account + "] are expired";
                throw new ExpiredCredentialsException(msg);
            }

        }

        return account;
    }

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = getUsername(principals);
        USERS_LOCK.readLock().lock();
        try {
            return this.users.get(username);
        } finally {
            USERS_LOCK.readLock().unlock();
        }
    }
}

 

3)自定義 Realm 

 由於 Realm 相當於 Datasource 數據源,SecurityManager 進行安全認證需要通過 Realm 獲取用戶身份數據,所以可以自定義 Realm 獲取用戶身份的信息;

public class CustomerRealm extends AuthorizingRealm {

    // 授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("====================");
        return null;
    }

    // 認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String) token.getPrincipal();
        System.out.println("principal = " + principal);
        //根據身份信息使用jdbc mybatis查詢相關數據庫
        if ("xiaochen".equalsIgnoreCase(principal)) {
            // 參數1:返回數據庫中的用戶名  參數2:返回數據庫中的正確密碼  參數3:提供當前的 realm 名字,this.getName()
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("wangwu", "123", this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }
}

 

4)使用自定義 Realm 進行認證

public class TestCustomerRealm {

    public static void main(String[] args) {
        // 1.創建安全管理器
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

        // 2.給安全管理器設置 realm 【數據庫中獲取用戶名和密碼】
        defaultSecurityManager.setRealm(new CustomerRealm());

        // 3.SecurityUtils 設置安全管理器
        SecurityUtils.setSecurityManager(defaultSecurityManager);

        // 4.獲取用戶登錄的主體
        Subject subject = SecurityUtils.getSubject();

        AuthenticationToken token = new UsernamePasswordToken("wangwu", "123");
        try {
            subject.login(token);
        } catch (UnknownAccountException e) {
            System.out.println("用戶名不正確");
            e.printStackTrace();
        } catch (IncorrectCredentialsException e) {
            System.out.println("用戶密碼不正確");
            e.printStackTrace();
        }
    }

}

 

5) 使用 MD5 和 Salt 

實際應用是將鹽和散列后的值存在數據庫中,自動realm從數據庫取出鹽和加密后的值由shiro完成密碼校驗。
  A 自定義 MD5 + salt 的 Realm 
public class CustomerMD5Realm extends AuthorizingRealm {

    /**
     * 一個主題可以有多個身份,參數是集合 principals;但是只有一個主身份;
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 獲取身份信息
        String principal = (String) principals.getPrimaryPrincipal();
        System.out.println("身份信息 = " + principal);
        
        return null;
    }

    // 認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String) token.getPrincipal();
        System.out.println("principal = " + principal);
        //根據身份信息使用jdbc mybatis查詢相關數據庫
        if ("wangwu".equalsIgnoreCase(principal)) {
            // 參數1:返回數據庫中的用戶名  參數2:返回數據庫中的正確密碼  參數3:注冊時的隨機鹽  參數4:提供當前的 realm 名字,this.getName()
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,
                    "e4f9bf3e0c58f045e62c23c533fcf633",
                    ByteSource.Util.bytes("X0*7ps"),
                    this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }
}    

 

  B 使用 MD5 + Salt 進行認證處理
public class TestCustomerMD5Authenticator {

    public static void main(String[] args) {
        // 1. 創建安全管理器
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        // 2. 給安全管理器設置 realm 【Realm 需要獲取用戶認證的數據源信息】
        CustomerMD5Realm customerMD5Realm = new CustomerMD5Realm();
        // 設置 realm 使用的 hash 憑證匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("md5"); // 設置使用的 Hash 算法
        credentialsMatcher.setHashIterations(1024);     // 需要散列多少次
        customerMD5Realm.setCredentialsMatcher(credentialsMatcher);
        securityManager.setRealm(customerMD5Realm);

        // 3. SecurityUtils 設置安全管理器
        SecurityUtils.setSecurityManager(securityManager);

        // 4. 獲取關鍵對象 Subject 主體
        Subject subject = SecurityUtils.getSubject();

        // 5. 創建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("wangwu", "123");

        // 用戶認證
        try {
            System.out.println("認證狀態: " + subject.isAuthenticated());
            subject.login(token);
            System.out.println("認證狀態: " + subject.isAuthenticated());
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("認證失敗: 用戶名不存在~");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("認證失敗: 密碼錯誤~");
        }
    }
}

  注意:深入了解 Shiro 的認證流程,可以 debug 運行程序,查看源代碼進行學習深究!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM