shiro 學習筆記

低吟不作語發表於2021-11-28

1. 許可權管理

1.1 什麼是許可權管理?

許可權管理實現對使用者訪問系統的控制,按照安全規則或者安全策略,可以控制使用者只能訪問自己被授權的資源

許可權管理包括使用者身份認證和授權兩部分,簡稱認證授權

1.2 什麼是身份認證?

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

1.3 什麼是授權?

授權,即訪問控制,控制誰能訪問哪些資源,主體進行身份認證後需要分配許可權方可訪問系統資源,對於某些資源沒有許可權是無法訪問的


2. 什麼是 shiro?

shiro 是一個功能強大且易於使用的 Java 安全框架,能夠完成身份認證、授權、加密和會話管理等功能

2.1 shiro 的核心架構

  • Subject

    主體,外部應用與 subject 進行互動,subject 記錄了當前操作使用者,將使用者的概念理解為當前操作的主體,可能是一個通過瀏覽器請求的使用者,也可能是一個執行的程式。Subject 在 shiro 中是一個介面,介面中定義了很多認證授相關的方法,外部程式通過 subject 進行認證授,而 subject 是通過 SecurityManager 安全管理器進行認證授權

  • SecurityManager

    安全管理器,對全部的 subject 進行安全管理,是 shiro 的核心。通過 SecurityManager 可以完成 subject 的認證、授權等,實質上 SecurityManager 是通過 Authenticator 進行認證,通過 Authorizer 進行授權,通過 SessionManager 進行會話管理。SecurityManager 是一個介面,繼承了 Authenticator,Authorizer,SessionManager 這三個介面

  • Authenticator

    認證器,對使用者身份進行認證,Authenticator 是一個介面,shiro 提供 ModularRealmAuthenticator 實現類,通過 ModularRealmAuthenticator 基本上可以滿足大多數需求,也可以自定義認證器

  • Authorizer

    授權器,使用者通過認證器認證通過,在訪問功能時需要通過授權器判斷使用者是否有此功能的操作許可權

  • Realm

    領域,相當於 datasource 資料來源,securityManager 進行安全認證需要通過 Realm 獲取使用者許可權資料,比如:如果使用者身份資料在資料庫,那麼 realm 就需要從資料庫獲取使用者身份資訊

  • SessionManager

    會話管理,shiro 框架定義了一套會話管理,它不依賴 web 容器的 session,所以 shiro 可以使用在非 web 應用上,也可以將分散式應用的會話集中在一點管理,此特性可使它實現單點登入

  • SessionDAO

    會話 dao,是對 session 會話操作的一套介面,比如要將 session 儲存到資料庫,可以通過 jdbc 將會話儲存到資料庫

  • CacheManager

    CacheManager 即快取管理,將使用者許可權資料儲存在快取,這樣可以提高效能

  • Cryptography

    密碼管理,shiro 提供了一套加解密的元件,方便開發,比如提供常用的雜湊、加解密等功能


3. shiro 中的認證

3.1 認證

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

3.2 shiro 認證中的關鍵物件

  • Subject:主體

    訪問系統的使用者,主體可以是使用者、程式等,進行認證的都稱為主體

  • Principal:身份資訊

    是主體(subject)進行身份認證的標識,標識必須具有唯一性, 如使用者名稱、手機號、郵箱地址等,一個主體可以有多個身份,但是必須有一個主身份(Primary Principal)

  • credential:憑證資訊

    是隻有主體自己知道的安全資訊,如密碼、證照等

3.3 認證流程

3.4 認證開發

3.4.1 引入依賴

<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-core</artifactId>
  <version>1.5.3</version>
</dependency>

3.4.2 建立配置檔案

該配置檔案用來書寫系統中相關的許可權資料,主要用於學習使用

[users]
xiaochen=123
zhangsan=456

3.4.3 開發認證程式碼

public class TestAuthenticator {

    public static void main(String[] args) {
        // 1.建立安全管理器物件
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 2.給安全管理器設定 realm
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        // 3.給 SecurityUtils 全域性安全工具類設定安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        // 4.獲取認證主體
        Subject subject = SecurityUtils.getSubject();
        // 5.建立令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        // 6.認證
        try {
            System.out.println("認證狀態:" + subject.isAuthenticated());
            subject.login(token);
            System.out.println("認證狀態:" + subject.isAuthenticated());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.5 自定義 Realm

自定義 Realm 的實現,即是將認證/授權資料來源轉為資料庫的實現

public class TestCustomerRealmAuthenticator {

    public static void main(String[] args) {
        // 1.建立安全管理器物件
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 2.設定自定義realm
        securityManager.setRealm(new CustomerRealm());
        // 3.給 SecurityUtils 全域性安全工具類設定安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        // 4.獲取認證主體
        Subject subject = SecurityUtils.getSubject();
        // 5.建立令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        // 6.認證
        try {
            System.out.println("認證狀態:" + subject.isAuthenticated());
            subject.login(token);
            System.out.println("認證狀態:" + subject.isAuthenticated());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

自定義 Realm 程式碼實現

public class CustomerRealm extends AuthorizingRealm {

    // 授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    // 認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 1. 在token中獲取使用者名稱
        String principal = (String) authenticationToken.getPrincipal();
        // 2. 查詢資料庫,此處模擬資料庫資料
        if ("xiaochen".equals(principal)) {
            // 引數1:正確的使用者名稱
            // 引數2:正確的密碼
            // 引數3:提供當前realm的名字
            SimpleAuthenticationInfo simpleAuthenticationInfo
                    = new SimpleAuthenticationInfo("xianchen", "123", this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }
}

3.6 明文加密

實際使用時,我們不可能把使用者密碼以明文形式顯示,需要做加密處理

通常的加密方式是使用 md5 + salt + hash 雜湊的形式,校驗過程:儲存鹽和雜湊後的值,在 shiro 完成密碼校驗

下面是使用 shiro 提供的 api 完成加密程式碼示例

public class TestShiroMD5 {

    public static void main(String[] args) {
        // 1. 使用md5加密
        // 引數1是明文密碼
        Md5Hash md5Hash1 = new Md5Hash("123");
        // 列印加密後的密文
        // 結果:202cb962ac59075b964b07152d234b70
        System.out.println(md5Hash1.toHex());
        // 2. 使用md5+salt加密
        Md5Hash md5Hash2 = new Md5Hash("123", "X0*7ps");
        // 8a83592a02263bfe6752b2b5b03a4799
        System.out.println(md5Hash2.toHex());
        // 3. 使用md5+salt+hash雜湊加密
        Md5Hash md5Hash3 = new Md5Hash("123", "X0*7ps", 1024);
        // e4f9bf3e0c58f045e62c23c533fcf633
        System.out.println(md5Hash3.toHex());
    }
}

自定義 CustomerMd5Realm

public class CustomerMd5Realm extends AuthorizingRealm {

    // 授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    // 認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 1. 在token中獲取使用者名稱
        String principal = (String) authenticationToken.getPrincipal();
        // 2. 查詢資料庫,此處模擬資料庫資料
        if ("xiaochen".equals(principal)) {
            // 引數1:正確的使用者名稱
            // 引數2:正確的密碼
            // 引數3:提供當前realm的名字
            // md5
            // return new SimpleAuthenticationInfo(principal, "202cb962ac59075b964b07152d234b70", this.getName());
            // md5+salt
            /*return new SimpleAuthenticationInfo(principal, "8a83592a02263bfe6752b2b5b03a4799", ByteSource.Util.bytes("X0*7ps"), this.getName());*/
        }
        return null;
    }
}

校驗流程

public class TestCustomerMd5RealmAuthenticator {

    public static void main(String[] args) {
        // 1.建立安全管理器物件
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 2.設定自定義realm
        CustomerMd5Realm realm = new CustomerMd5Realm();
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 使用MD5加密
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 雜湊次數
        hashedCredentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(hashedCredentialsMatcher);
        securityManager.setRealm(realm);
        // 3.給 SecurityUtils 全域性安全工具類設定安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        // 4.獲取認證主體
        Subject subject = SecurityUtils.getSubject();
        // 5.建立令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        // 6.認證
        try {
            System.out.println("認證狀態:" + subject.isAuthenticated());
            subject.login(token);
            System.out.println("認證狀態:" + subject.isAuthenticated());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4. shiro 中的授權

4.1 授權

授權,即訪問控制,控制誰能訪問哪些資源。主體進行身份認證後需要分配許可權方可訪問系統的資源,對於某些資源沒有許可權是無法訪問的

4.2 關鍵物件

授權可簡單理解為 who 對 what(which) 進行 How 操作:

  • Who,即主體(Subject),主體需要訪問系統中的資源
  • What,即資源(Resource),如系統選單、頁面、按鈕、類方法、系統商品資訊等。資源包括資源型別和資源例項,比如商品資訊為資源型別,型別為 t01 的商品為資源例項,編號為 001 的商品資訊也屬於資源例項
  • How,許可權/許可(Permission),規定了主體對資源的操作許可,許可權離開資源沒有意義,如使用者查詢許可權、使用者新增許可權、某個類方法的呼叫許可權、編號為 001 使用者的修改許可權等,通過許可權可知道主體對哪些資源都有哪些操作許可

4.3 授權方式

基於角色的訪問控制,以角色為中心進行訪問控制

if(subject.hasRole("admin")){
   //操作什麼資源
}

基於資源的訪問控制,以資源為中心進行訪問控制

if(subject.isPermission("user:update:01")){ //資源例項
	//對01使用者進行修改
}
if(subject.isPermission("user:update:*")){  //資源型別
	//對01使用者進行修改
}

4.4 許可權字串

許可權字串的規則是:資源識別符號:操作:資源例項識別符號,意思是對哪個資源的哪個例項具有什麼操作,: 是資源/操作/例項的分割符,許可權字串也可以使用 * 萬用字元

例子:

  • 使用者建立許可權:user:create,或 user:create:*
  • 使用者修改例項 001 的許可權:user:update:001
  • 使用者例項 001 的所有許可權:user:*:001

4.5 授權程式設計實現

在之前 md5 加密的基礎上,實現授權操作

自定義 CustomerMd5Realm

public class CustomerMd5Realm extends AuthorizingRealm {

    // 授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 獲取身份資訊
        String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
        // 根據身份資訊(使用者名稱)從資料庫獲取當前使用者的角色以及許可權資訊
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        // 將資料庫中查詢的角色資訊賦值給許可權物件
        simpleAuthorizationInfo.addRole("admin");
        simpleAuthorizationInfo.addRole("user");
        // 將資料庫中查詢的許可權資訊賦值給許可權物件
        simpleAuthorizationInfo.addStringPermission("user:*:01");
        simpleAuthorizationInfo.addStringPermission("product:create:02");
        return simpleAuthorizationInfo;
    }

    ...
}

授權邏輯

public class TestCustomerMd5RealmAuthenticator {

    public static void main(String[] args) {
        
        ...

        // 7. 認證使用者進行授權
        if (subject.isAuthenticated()) {
            // 7.1 基於角色許可權控制
            boolean hasRole = subject.hasRole("admin");
            System.out.println("角色校驗:" + hasRole);
            // 7.2 基於多角色許可權控制
            boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("admin", "user"));
            System.out.println("多角色校驗:" + hasAllRoles);
            // 7.3 是否具有其中一個角色
            boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super"));
            for (boolean aBoolean : booleans) {
                System.out.println(aBoolean);
            }
            // 7.4 基於許可權字串的訪問控制
            boolean permitted = subject.isPermitted("user:*:01");
            System.out.println("資源許可權校驗:" + permitted);
            // 7.5 分佈具有哪些資源許可權
            boolean[] permitted1 = subject.isPermitted("user:*:01", "order:*:10");
            for (boolean b : permitted1) {
                System.out.println(b);
            }
            // 7.6 同時具有哪些資源許可權
            boolean permittedAll = subject.isPermittedAll("user:*:01", "product:*");
            System.out.println("多資源許可權校驗:" + permittedAll);
        }
    }
}

相關文章