1. 程式人生 > 實用技巧 >shiro詳解-shiro史上最全學習筆記

shiro詳解-shiro史上最全學習筆記

1.shiro簡介

1.1.基本功能點

Shiro 可以非常容易的開發出足夠好的應用,其不僅可以用在 JavaSE 環境,也可以用在 JavaEE 環境。Shiro 可以幫助我們完成:認證、授權、加密、會話管理、與 Web 整合、快取等。其基本功能點如下圖所示:

  • Authentication:身份認證 / 登入,驗證使用者是不是擁有相應的身份;
  • Authorization:授權,即許可權驗證,驗證某個已認證的使用者是否擁有某個許可權;即判斷使用者是否能做事情,常見的如:驗證某個使用者是否擁有某個角色。或者細粒度的驗證某個使用者對某個資源是否具有某個許可權;
  • Session Manager:會話管理,即使用者登入後就是一次會話,在沒有退出之前,它的所有資訊都在會話中;會話可以是普通 JavaSE 環境的,也可以是如 Web 環境的;
  • Cryptography:加密,保護資料的安全性,如密碼加密儲存到資料庫,而不是明文儲存;
  • Web Support:Web 支援,可以非常容易的整合到 Web 環境;
  • Caching:快取,比如使用者登入後,其使用者資訊、擁有的角色 / 許可權不必每次去查,這樣可以提高效率;
  • Concurrency:shiro 支援多執行緒應用的併發驗證,即如在一個執行緒中開啟另一個執行緒,能把許可權自動傳播過去;
  • Testing:提供測試支援;
  • Run As:允許一個使用者假裝為另一個使用者(如果他們允許)的身份進行訪問;
  • Remember Me:記住我,這個是非常常見的功能,即一次登入後,下次再來的話不用登入了。

記住一點,Shiro 不會去維護使用者、維護許可權;這些需要我們自己去設計 / 提供;然後通過相應的介面注入給 Shiro 即可。

1.2.Shiro的架構

1.2.1.外部

我們從外部來看 Shiro ,即從應用程式角度的來觀察如何使用 Shiro 完成工作。如下圖:

可以看到:應用程式碼直接互動的物件是 Subject,也就是說 Shiro 的對外 API 核心就是 Subject;其每個 API 的含義:

Subject:主體,代表了當前 “使用者”,這個使用者不一定是一個具體的人,與當前應用互動的任何東西都Subject,如網路爬蟲,機器人等;即一個抽象概念;所有 Subject 都繫結到 SecurityManager,與 Subject 的所有互動都會委託給 SecurityManager;可以把 Subject 認為是一個門面;SecurityManager 才是實際的執行者;

SecurityManager:安全管理器;即所有與安全有關的操作都會與 SecurityManager 互動;且它管理著所有 Subject;可以看出它是 Shiro 的核心,它負責與後邊介紹的其他元件進行互動,如果學習過 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;

Realm:域,Shiro 從 Realm 獲取安全資料(如使用者、角色、許可權),就是說 SecurityManager 要驗證使用者身份,那麼它需要從 Realm 獲取相應的使用者進行比較以確定使用者身份是否合法;也需要從 Realm 得到使用者相應的角色 / 許可權進行驗證使用者是否能進行操作;可以把 Realm 看成 DataSource,即安全資料來源。

也就是說對於我們而言,最簡單的一個 Shiro 應用:

  1. 應用程式碼通過 Subject 來進行認證和授權,而 Subject 又委託給 SecurityManager;
  2. 我們需要給 Shiro 的 SecurityManager 注入 Realm,從而讓 SecurityManager 能得到合法的使用者及其許可權進行判斷。

從以上也可以看出,Shiro 不提供維護使用者 / 許可權,而是通過 Realm 讓開發人員自己注入。

1.2.2.內部

接下來我們來從 Shiro 內部來看下 Shiro 的架構,如下圖所示:

Subject:主體,可以看到主體可以是任何可以與應用互動的 “使用者”;

SecurityManager:相當於 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心臟;所有具體的互動都通過 SecurityManager 進行控制;它管理著所有 Subject、且負責進行認證和授權、及會話、快取的管理。

Authenticator:認證器,負責主體認證的,這是一個擴充套件點,如果使用者覺得 Shiro 預設的不好,可以自定義實現;其需要認證策略(Authentication Strategy),即什麼情況下算使用者認證通過了;

Authrizer:授權器,或者訪問控制器,用來決定主體是否有許可權進行相應的操作;即控制著使用者能訪問應用中的哪些功能;

Realm:可以有 1 個或多個 Realm,可以認為是安全實體資料來源,即用於獲取安全實體的;可以是 JDBC 實現,也可以是 LDAP 實現,或者記憶體實現等等;由使用者提供;注意:Shiro 不知道你的使用者 / 許可權儲存在哪及以何種格式儲存;所以我們一般在應用中都需要實現自己的 Realm;

SessionManager:如果寫過 Servlet 就應該知道 Session 的概念,Session 呢需要有人去管理它的生命週期,這個元件就是 SessionManager;而 Shiro 並不僅僅可以用在 Web 環境,也可以用在如普通的 JavaSE 環境、EJB 等環境;所有呢,Shiro 就抽象了一個自己的 Session 來管理主體與應用之間互動的資料;這樣的話,比如我們在 Web 環境用,剛開始是一臺 Web 伺服器;接著又上了臺 EJB 伺服器;這時想把兩臺伺服器的會話資料放到一個地方,這個時候就可以實現自己的分散式會話(如把資料放到 Memcached 伺服器);

SessionDAO:DAO 大家都用過,資料訪問物件,用於會話的 CRUD,比如我們想把 Session 儲存到資料庫,那麼可以實現自己的 SessionDAO,通過如 JDBC 寫到資料庫;比如想把 Session 放到 Memcached 中,可以實現自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 進行快取,以提高效能;

CacheManager:快取控制器,來管理如使用者、角色、許可權等的快取的;因為這些資料基本上很少去改變,放到快取中後可以提高訪問的效能

Cryptography:密碼模組,Shiro 提高了一些常見的加密元件用於如密碼加密 / 解密的。

2.shiro元件

2.1.身份驗證

身份驗證,即在應用中誰能證明他就是他本人。一般提供如他們的身份 ID 一些標識資訊來表明他就是他本人,如提供身份證,使用者名稱 / 密碼來證明。

在 shiro 中,使用者需要提供 principals (身份)和 credentials(證明)給 shiro,從而應用能驗證使用者身份:

principals:身份,即主體的標識屬性,可以是任何東西,如使用者名稱、郵箱等,唯一即可。一個主體可以有多個 principals,但只有一個 Primary principals,一般是使用者名稱 / 密碼 / 手機號。

credentials:證明 / 憑證,即只有主體知道的安全值,如密碼 / 數字證書等。

最常見的 principals 和 credentials 組合就是使用者名稱 / 密碼了。接下來先進行一個基本的身份認證。

另外兩個相關的概念是之前提到的Subject及Realm,分別是主體及驗證主體的資料來源。

2.1.1.maven依賴配置

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <shiro.version>1.2.2</shiro.version>
</properties>

<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
  </dependency>
  <!-- log4j -->
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.2</version>
  </dependency>
  <!-- shiro相關依賴 -->
  <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>${shiro.version}</version>
  </dependency>
  <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>${shiro.version}</version>
  </dependency>
  <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
  </dependency>
  <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-quartz</artifactId>
    <version>${shiro.version}</version>
  </dependency>
  <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.3</version>
    </dependency>
</dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

2.1.2.登入/退出

1、準備一些使用者身份

[users]
zhang=123
wang=123
  • 1
  • 2
  • 3

此處使用 ini 配置檔案,通過 [users] 指定了兩個主體:zhang/123、wang/123。

 @Test
    public void  testLoginLoginout(){
        //1、獲取SecurityManager工廠,此處使用Ini配置檔案初始化SecurityManager
        Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //2、得到SecurityManager例項 並繫結給SecurityUtils
        org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        //3、得到Subject及建立使用者名稱/密碼身份驗證Token(即使用者身份/憑證)
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
        try {
            //4、登入,即身份驗證
            subject.login(token);
        } catch (AuthenticationException e) {
            //5、身份驗證失敗
        }
        System.out.println(subject.isAuthenticated());
        //6、退出
        subject.logout();

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 首先通過 new IniSecurityManagerFactory 並指定一個 ini 配置檔案來建立一個 SecurityManager 工廠;
  • 接著獲取 SecurityManager 並繫結到 SecurityUtils,這是一個全域性設定,設定一次即可;
  • 通過 SecurityUtils 得到 Subject,其會自動繫結到當前執行緒;如果在 web 環境在請求結束時需要解除繫結;然後獲取身份驗證的 Token,如使用者名稱 / 密碼;
  • 呼叫 subject.login 方法進行登入,其會自動委託給 SecurityManager.login 方法進行登入;
  • 如果身份驗證失敗請捕獲 AuthenticationException 或其子類,常見的如: DisabledAccountException(禁用的帳號)、LockedAccountException(鎖定的帳號)、UnknownAccountException(錯誤的帳號)、ExcessiveAttemptsException(登入失敗次數過多)、IncorrectCredentialsException (錯誤的憑證)、ExpiredCredentialsException(過期的憑證)等,具體請檢視其繼承關係;對於頁面的錯誤訊息展示,最好使用如 “使用者名稱 / 密碼錯誤” 而不是 “使用者名稱錯誤”/“密碼錯誤”,防止一些惡意使用者非法掃描帳號庫;
  • 最後可以呼叫 subject.logout 退出,其會自動委託給 SecurityManager.logout 方法退出。

從如上程式碼可總結出身份驗證的步驟:

  1. 收集使用者身份 / 憑證,即如使用者名稱 / 密碼;
  2. 呼叫 Subject.login 進行登入,如果失敗將得到相應的 AuthenticationException 異常,根據異常提示使用者錯誤資訊;否則登入成功;
  3. 最後呼叫 Subject.logout 進行退出操作。

2.1.3.身份認證流程

流程如下:

  1. 首先呼叫 Subject.login(token) 進行登入,其會自動委託給 Security Manager,呼叫之前必須通過 SecurityUtils.setSecurityManager() 設定;
  2. SecurityManager 負責真正的身份驗證邏輯;它會委託給 Authenticator 進行身份驗證;
  3. Authenticator 才是真正的身份驗證者,Shiro API 中核心的身份認證入口點,此處可以自定義插入自己的實現;
  4. Authenticator 可能會委託給相應的 AuthenticationStrategy 進行多 Realm 身份驗證,預設 ModularRealmAuthenticator 會呼叫 AuthenticationStrategy 進行多 Realm 身份驗證;
  5. Authenticator 會把相應的 token 傳入 Realm,從 Realm 獲取身份驗證資訊,如果沒有返回 / 丟擲異常表示身份驗證失敗了。此處可以配置多個 Realm,將按照相應的順序及策略進行訪問。

2.1.4.Realm

Realm:域,Shiro 從從 Realm 獲取安全資料(如使用者、角色、許可權),就是說 SecurityManager 要驗證使用者身份,那麼它需要從 Realm 獲取相應的使用者進行比較以確定使用者身份是否合法;也需要從 Realm 得到使用者相應的角色 / 許可權進行驗證使用者是否能進行操作;可以把 Realm 看成 DataSource,即安全資料來源。如我們之前的 ini 配置方式將使用 org.apache.shiro.realm.text.IniRealm。

2.1.4.1.單 Realm 配置

1、自定義 Realm 實現(需要實現四個方法)

public class MyRealm extends AuthorizingRealm {
    /**
     * 獲取角色與許可權
     *doGetAuthorizationInfo執行時機有三個,如下:
     *  1、subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去呼叫這個是否有什麼角色或者是否有什麼許可權的時候;
     *  2、@RequiresRoles("admin") :在方法上加註解的時候;
     *  3、@shiro.hasPermission name = "admin"/@shiro.hasPermission:"dustin:test"在頁面上加shiro標籤的時候,即進這個頁面的時候掃描到有這個標籤的時候。
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    /**
     * 登入資訊驗證
     
     * 1.doGetAuthenticationInfo執行時機如下
     * 當呼叫Subject currentUser = SecurityUtils.getSubject();
     * currentUser.login(token);
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String username = (String)token.getPrincipal();
       String password = new String((char[])token.getCredentials());

        if(!"zhang".equals(username)) {
            throw new UnknownAccountException(); //如果使用者名稱錯誤
        }
        if(!"123".equals(password)) {
            throw new IncorrectCredentialsException(); //如果密碼錯誤
        }


       return new SimpleAuthenticationInfo(username, password, getName());
    }


    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }

    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }

    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }

    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();

    }
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

2、ini 配置檔案指定自定義 Realm 實現 (shiro-realm.ini)

#宣告一個realm
myRealm=net.wanho.realm.MyRealm
#指定securityManager的realms實現
securityManager.realms=$myRealm
  • 1
  • 2
  • 3
  • 4

3、測試

只需要把之前的 shiro.ini 配置檔案改成 shiro-realm.ini 即可。

 @Test
    public void  testLoginLoginout(){
        //1、獲取SecurityManager工廠,此處使用Ini配置檔案初始化SecurityManager
        Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
        //2、得到SecurityManager例項 並繫結給SecurityUtils
        org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        //3、得到Subject及建立使用者名稱/密碼身份驗證Token(即使用者身份/憑證)
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
        try {
            //4、登入,即身份驗證
            subject.login(token);
        } catch (AuthenticationException e) {
            //5、身份驗證失敗
        }
        System.out.println(subject.isAuthenticated());
        //6、退出
        subject.logout();

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

2.1.4.2.多Realm 配置

1、ini 配置檔案(shiro-multi-realm.ini)

#宣告一個realm
myRealm1=net.wanho.realm.MyRealm1
myRealm2=net.wanho.realm.MyRealm2
#指定securityManager的realms實現
securityManager.realms=$myRealm1,$myRealm2
  • 1
  • 2
  • 3
  • 4
  • 5

注:securityManager 會按照 realms 指定的順序進行身份認證。此處我們使用顯示指定順序的方式指定了 Realm 的順序,如果刪除 “securityManager.realms=myRealm1,myRealm2”,那麼securityManager 會按照 realm 宣告的順序進行使用(即無需設定 realms 屬性,其會自動發現),當我們顯示指定 realm 後,其他沒有指定 realm 將被忽略,如 “securityManager.realms=$myRealm1”,那麼 myRealm2 不會被自動設定進去。

2、測試

測試方法同單 Realm 配置方法一致

2.1.4.3.Shiro 預設提供的 Realm

其中主要預設實現如下:

org.apache.shiro.realm.text.IniRealm:[users] 部分指定使用者名稱 / 密碼及其角色;[roles] 部分指定角色即許可權資訊;

org.apache.shiro.realm.text.PropertiesRealm: user.username=password,role1,role2 指定使用者名稱 / 密碼及其角色;role.role1=permission1,permission2 指定角色及許可權資訊;

org.apache.shiro.realm.jdbc.JdbcRealm:通過 sql 查詢相應的資訊,如 “select password from users where username = ?” 獲取使用者密碼,“select password, password_salt from users where username = ?” 獲取使用者密碼及鹽;“select role_name from user_roles where username = ?” 獲取使用者角色;“select permission from roles_permissions where role_name = ?” 獲取角色對應的許可權資訊;也可以呼叫相應的 api 進行自定義 sql;

2.1.4.4.JDBC Realm 使用

1、資料庫及依賴

使用 mysql 資料庫及 druid 連線池

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.18</version>
</dependency>
<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.0.11</version>
 </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2、到資料庫 shiro 下建三張表:users(使用者名稱 / 密碼)、user_roles(使用者 / 角色)、roles_permissions(角色 / 許可權),並新增一個使用者記錄,使用者名稱 / 密碼為 zhang/123;

3、ini 配置(shiro-jdbc-realm.ini)

jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiro
dataSource.username=root
dataSource.password=11111
jdbcRealm.dataSource=$dataSource
securityManager.realms=$jdbcRealm
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注:

  • 變數名 = 全限定類名會自動建立一個類例項 全限定類名會自動建立一個類例項
  • 變數名. 屬性 = 值 自動呼叫相應的 setter 方法進行賦值
  • $ 變數名 引用之前的一個物件例項

4、測試

 @Test
    public void  testLoginLoginout(){
        //1、獲取SecurityManager工廠,此處使用Ini配置檔案初始化SecurityManager
        Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-jdbc-realm.ini");
        //2、得到SecurityManager例項 並繫結給SecurityUtils
        org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        //3、得到Subject及建立使用者名稱/密碼身份驗證Token(即使用者身份/憑證)
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
        try {
            //4、登入,即身份驗證
            subject.login(token);
        } catch (AuthenticationException e) {
            //5、身份驗證失敗
        }
        System.out.println(subject.isAuthenticated());
        //6、退出
        subject.logout();

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

2.1.5.Authenticator 及 AuthenticationStrategy

Authenticator 的職責是驗證使用者帳號,是 Shiro API 中身份驗證核心的入口點,

如果驗證成功,將返回 AuthenticationInfo 驗證資訊;此資訊中包含了身份及憑證;如果驗證失敗將丟擲相應的AuthenticationException 實現。

SecurityManager 介面繼承了 Authenticator,另外還有一個 ModularRealmAuthenticator 實現,其委託給多個 Realm 進行驗證,驗證規則通過 AuthenticationStrategy 介面指定,預設提供的實現:

FirstSuccessfulStrategy:只要有一個 Realm 驗證成功即可,只返回第一個 Realm 身份驗證成功的認證資訊,其他的忽略;

AtLeastOneSuccessfulStrategy:只要有一個 Realm 驗證成功即可,和 FirstSuccessfulStrategy 不同,返回所有 Realm 身份驗證成功的認證資訊;

AllSuccessfulStrategy:所有 Realm 驗證成功才算成功,且返回所有 Realm 身份驗證成功的認證資訊,如果有一個失敗就失敗了。

**注:**ModularRealmAuthenticator 預設使用 AtLeastOneSuccessfulStrategy 策略。

假設我們有三個 realm:
myRealm1: 使用者名稱 / 密碼為 zhang/123 時成功,且返回身份 / 憑據為 zhang/123;
myRealm2: 使用者名稱 / 密碼為 wang/123 時成功,且返回身份 / 憑據為 wang/123;
myRealm3: 使用者名稱 / 密碼為 zhang/123 時成功,且返回身份 / 憑據為[email protected]/123,和 myRealm1 不同的是返回時的身份變了;

1、ini 配置檔案 (shiro-authenticator-all-success.ini)

#指定securityManager的authenticator實現
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator
#指定securityManager.authenticator的authenticationStrategy
allSuccessfulStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy

myRealm=net.wanho.realm.MyRealm
myRealm2=net.wanho.realm.MyRealm2
myRealm3=net.wanho.realm.MyRealm3
securityManager.realms=$myRealm,$myRealm3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2、新建realm

MyRealm2

public class MyRealm2 extends AuthorizingRealm {
    /**
     * 獲取角色與許可權
     *doGetAuthorizationInfo執行時機有三個,如下:
     *  1、subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去呼叫這個是否有什麼角色或者是否有什麼許可權的時候;
     *  2、@RequiresRoles("admin") :在方法上加註解的時候;
     *  3、@shiro.hasPermission name = "admin"/@shiro.hasPermission:"dustin:test"在頁面上加shiro標籤的時候,即進這個頁面的時候掃描到有這個標籤的時候。
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    /**
     * 登入資訊驗證
     *
     * 1.doGetAuthenticationInfo執行時機如下
     * 當呼叫Subject currentUser = SecurityUtils.getSubject();
     * currentUser.login(token);
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String username = (String)token.getPrincipal();
       String password = new String((char[])token.getCredentials());

        if(!"wang".equals(username)) {
            throw new UnknownAccountException(); //如果使用者名稱錯誤
        }
        if(!"123".equals(password)) {
            throw new IncorrectCredentialsException(); //如果密碼錯誤
        }


       return new SimpleAuthenticationInfo(username, password, getName());
    }


    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }

    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }

    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }

    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();

    }
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

MyRealm3

public class MyRealm extends AuthorizingRealm {
    /**
     * 獲取角色與許可權
     *doGetAuthorizationInfo執行時機有三個,如下:
     *  1、subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去呼叫這個是否有什麼角色或者是否有什麼許可權的時候;
     *  2、@RequiresRoles("admin") :在方法上加註解的時候;
     *  3、@shiro.hasPermission name = "admin"/@shiro.hasPermission:"dustin:test"在頁面上加shiro標籤的時候,即進這個頁面的時候掃描到有這個標籤的時候。
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    /**
     * 登入資訊驗證
     *
     * 1.doGetAuthenticationInfo執行時機如下
     * 當呼叫Subject currentUser = SecurityUtils.getSubject();
     * currentUser.login(token);
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String username = (String)token.getPrincipal();
       String password = new String((char[])token.getCredentials());

        if(!"zhang".equals(username)) {
            throw new UnknownAccountException(); //如果使用者名稱錯誤
        }
        if(!"123".equals(password)) {
            throw new IncorrectCredentialsException(); //如果密碼錯誤
        }


       return new SimpleAuthenticationInfo("[email protected]", password, getName());
    }


    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }

    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }

    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }

    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();

    }
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

3、測試

  • 首先通用化登入邏輯
private void login(String configFile) {
    //1、獲取SecurityManager工廠,此處使用Ini配置檔案初始化SecurityManager
    Factory<org.apache.shiro.mgt.SecurityManager> factory =
            new IniSecurityManagerFactory(configFile);
    //2、得到SecurityManager例項 並繫結給SecurityUtils
    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    //3、得到Subject及建立使用者名稱/密碼身份驗證Token(即使用者身份/憑證)
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
    subject.login(token);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  /**
     * 測試驗證規則(可在配置檔案中修改)
     *
     * FirstSuccessfulStrategy:只要有一個 Realm 驗證成功即可,只返回第一個 Realm 身份驗證成功的認證資訊,其他的忽略;
        AtLeastOneSuccessfulStrategy:只要有一個 Realm 驗證成功即可,和 FirstSuccessfulStrategy 不同,返回所有 Realm 身份驗證成功的認證資訊;
        AllSuccessfulStrategy:所有 Realm 驗證成功才算成功,且返回所有 Realm 身份驗證成功的認證資訊,如果有一個失敗就失敗了。

     */
    @Test
    public void testAllSuccessfulStrategyWithSuccess() {
        login("classpath:shiro-authenticator-all-success.ini");
        Subject subject = SecurityUtils.getSubject();
        //得到一個身份集合,其包含了Realm驗證成功的身份資訊
        PrincipalCollection principalCollection = subject.getPrincipals();
        for (Object principal : principalCollection) {
            System.out.println(principal.toString());
        }
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

4、自定義AuthenticationStrategy實現,首先看其API:

//在所有Realm驗證之前呼叫  
AuthenticationInfo beforeAllAttempts(  
Collection<? extends Realm> realms, AuthenticationToken token)   
throws AuthenticationException;  
//在每個Realm之前呼叫  
AuthenticationInfo beforeAttempt(  
Realm realm, AuthenticationToken token, AuthenticationInfo aggregate)   
throws AuthenticationException;  
//在每個Realm之後呼叫  
AuthenticationInfo afterAttempt(  
Realm realm, AuthenticationToken token,   
AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t)  
throws AuthenticationException;  
//在所有Realm之後呼叫  
AuthenticationInfo afterAllAttempts(  
AuthenticationToken token, AuthenticationInfo aggregate)   
throws AuthenticationException;   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

自定義實現時一般繼承org.apache.shiro.authc.pam.AbstractAuthenticationStrategy即可

2.2.授權

授權,也叫訪問控制,即在應用中控制誰能訪問哪些資源(如訪問頁面/編輯資料/頁面操作等)。在授權中需瞭解的幾個關鍵物件:主體(Subject)、資源(Resource)、許可權(Permission)、角色(Role)。

主體
主體,即訪問應用的使用者,在 Shiro 中使用 Subject 代表該使用者。使用者只有授權後才允許訪問相應的資源。

資源
在應用中使用者可以訪問的任何東西,比如訪問 JSP 頁面、檢視/編輯某些資料、訪問某個業務方法、列印文字等等都是資源。使用者只要授權後才能訪問。

許可權
安全策略中的原子授權單位,通過許可權我們可以表示在應用中使用者有沒有操作某個資源的權力。即許可權表示在應用中使用者能不能訪問某個資源,如: 訪問使用者列表頁面
檢視/新增/修改/刪除使用者資料(即很多時候都是 CRUD(增查改刪)式許可權控制)
列印文件等等。。。

如上可以看出,許可權代表了使用者有沒有操作某個資源的權利,即反映在某個資源上的操作允不允許,不反映誰去執行這個操作。所以後續還需要把許可權賦予給使用者,即定義哪個使用者允許在某個資源上做什麼操作(許可權),Shiro 不會去做這件事情,而是由實現人員提供。

Shiro 支援粗粒度許可權(如使用者模組的所有許可權)和細粒度許可權(操作某個使用者的許可權,即例項級別的),後續部分介紹。

角色
角色代表了操作集合,可以理解為許可權的集合,一般情況下我們會賦予使用者角色而不是許可權,即這樣使用者可以擁有一組許可權,賦予許可權時比較方便。典型的如:專案經理、技術總監、CTO、開發工程師等都是角色,不同的角色擁有一組不同的許可權。

隱式角色:
即直接通過角色來驗證使用者有沒有操作許可權,如在應用中 CTO、技術總監、開發工程師可以使用印表機,假設某天不允許開發工程師使用印表機,此時需要從應用中刪除相應程式碼;再如在應用中 CTO、技術總監可以檢視使用者、檢視許可權;突然有一天不允許技術總監檢視使用者、檢視許可權了,需要在相關程式碼中把技術總監角色從判斷邏輯中刪除掉;即粒度是以角色為單位進行訪問控制的,粒度較粗;如果進行修改可能造成多處程式碼修改。

顯示角色:
在程式中通過許可權控制誰能訪問某個資源,角色聚合一組許可權集合;這樣假設哪個角色不能訪問某個資源,只需要從角色代表的許可權集合中移除即可;無須修改多處程式碼;即粒度是以資源/例項為單位的;粒度較細。

2.2.1.授權方式

Shiro 支援三種方式的授權:

1.程式設計式:通過寫 if/else 授權程式碼塊完成:

Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
    //有許可權
} else {
    //無許可權
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.註解式:通過在執行的 Java 方法上放置相應的註解完成:

@RequiresRoles("admin")
public void hello() {
    //有許可權
};
  • 1
  • 2
  • 3
  • 4

3.SP/GSP 標籤:在 JSP/GSP 頁面通過相應的標籤完成:

<shiro:hasRole name="admin">
<!— 有許可權 —>
</shiro:hasRole>;
  • 1
  • 2
  • 3

2.2.1.1.基於角色的訪問控制(隱式角色)

1、在 ini 配置檔案配置使用者擁有的角色(shiro-role.ini)

[users]
zhang=123,role1,role2
wang=123,role1
  • 1
  • 2
  • 3

規則即:“使用者名稱=密碼,角色1,角色2”,如果需要在應用中判斷使用者是否有相應角色,就需要在相應的 Realm 中返回角色資訊,也就是說 Shiro 不負責維護使用者-角色資訊,需要應用提供,Shiro 只是提供相應的介面方便驗證。

2、測試

登入方法

private Subject login(String configFile,String userName,String password) {
    //1、獲取SecurityManager工廠,此處使用Ini配置檔案初始化SecurityManager
    Factory<org.apache.shiro.mgt.SecurityManager> factory =
            new IniSecurityManagerFactory(configFile);
    //2、得到SecurityManager例項 並繫結給SecurityUtils
    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    //3、得到Subject及建立使用者名稱/密碼身份驗證Token(即使用者身份/憑證)
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
    subject.login(token);
    return subject;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Shiro提供了hasRole/hasRole用於判斷使用者是否擁有某個角色/某些許可權;

 @Test
    public void testHasRole() {
        Subject subject = login("classpath:shiro-role.ini", "zhang", "123");
        //判斷擁有角色:role1
        System.out.println(subject.hasRole("role1"));
        //判斷擁有角色:role1 and role2
        System.out.println(subject.hasAllRoles(Arrays.asList("role1", "role2")));
        //判斷擁有角色:role1 and role2 and !role3
        boolean[] result = subject.hasRoles(Arrays.asList("role1", "role2", "role3"));
        System.out.println(result[0]);
        System.out.println(result[1]);
        System.out.println(result[2]);

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Shiro提供的checkRole/checkRoles和hasRole/hasAllRoles不同的地方是它在判斷為假的情況下會丟擲UnauthorizedException異常。

@Test(expected = UnauthorizedException.class)
    public void testCheckRole() {
        Subject subject = login("classpath:shiro-role.ini", "zhang", "123");
        //判斷是否擁有角色:role1
        subject.checkRole("role1");
        //判斷是否擁有角色:role1 and role3 失敗丟擲異常
        subject.checkRoles("role1", "role3");
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

**注:**到此基於角色的訪問控制(即隱式角色)就完成了,這種方式的缺點就是如果很多地方進行了角色判斷,但是有一天不需要了那麼就需要修改相應程式碼把所有相關的地方進行刪除;這就是粗粒度造成的問題。

2.2.1.2.基於角色的訪問控制(顯式角色)

1、在ini配置檔案配置使用者擁有的角色及角色-許可權關係(shiro-permission.ini)

[users]  
zhang=123,role1,role2  
wang=123,role1  
[roles]  
role1=user:create,user:update  
role2=user:create,user:delete  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

規則:“使用者名稱=密碼,角色1,角色2”“角色=許可權1,許可權2”,即首先根據使用者名稱找到角色,然後根據角色再找到許可權;即角色是許可權集合;Shiro同樣不進行許可權的維護,需要我們通過Realm返回相應的許可權資訊。只需要維護“使用者——角色”之間的關係即可。

2、測試

Shiro提供了isPermitted和isPermittedAll用於判斷使用者是否擁有某個許可權或所有許可權

  
@Test
    public void testIsPermitted() {
        Subject subject = login("classpath:shiro-permission.ini", "zhang", "123");
        //判斷擁有許可權:user:create
        System.out.println(subject.isPermitted("user:create"));
        //判斷擁有許可權:user:update and user:delete
        System.out.println(subject.isPermittedAll("user:update", "user:delete"));
        //判斷沒有許可權:user:view
        System.out.println(subject.isPermitted("user:view"));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
@Test(expected = UnauthorizedException.class)  
public void testCheckPermission () {  
    Subject subject = login("classpath:shiro-permission.ini", "zhang", "123");
    //斷言擁有許可權:user:create  
    subject.checkPermission("user:create");  
    //斷言擁有許可權:user:delete and user:update  
    subject.checkPermissions("user:delete", "user:update");  
    //斷言擁有許可權:user:view 失敗丟擲異常  
    subject.checkPermissions("user:view");  
}   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

失敗的情況下會丟擲UnauthorizedException異常

到此基於資源的訪問控制(顯示角色)就完成了,也可以叫基於許可權的訪問控制,這種方式的一般規則是“資源識別符號:操作”,即是資源級別的粒度;這種方式的好處就是如果要修改基本都是一個資源級別的修改,不會對其他模組程式碼產生影響,粒度小。但是實現起來可能稍微複雜點,需要維護“使用者——角色,角色——許可權(資源:操作)”之間的關係。

2.2.2.Permission許可權

字串萬用字元許可權

規則:“資源識別符號:操作:物件例項ID” 即對哪個資源的哪個例項可以進行什麼操作。其預設支援萬用字元許可權字串,“:”表示資源/操作/例項的分割;“,”表示操作的分割;“*”表示任意資源/操作/例項。

2.2.2.1.單個資源單個許可權

subject.checkPermissions("system:user:update");  
  • 1

使用者擁有資源“system:user”的“update”許可權。

2.2.2.2.單個資源多個許可權

1.ini配置檔案 (兩種寫法)

role41=system:user:update,system:user:delete 
#或者
role41="system:user:update,delete"
  • 1
  • 2
  • 3

2.然後通過如下程式碼判斷

subject.checkPermissions("system:user:update", "system:user:delete");
//或者
subject.checkPermissions("system:user:update,delete");
  • 1
  • 2
  • 3

通過“system:user:update,delete”驗證"system:user:update, system:user:delete"是沒問題的,但是反過來是規則不成立。

2.2.2.3.單個資源全部許可權

1.ini配置檔案 (兩種寫法)

role51="system:user:create,update,delete,view"
#或者
role51=system:user:*  
  • 1
  • 2
  • 3

2.然後通過如下程式碼判斷

subject.checkPermissions("system:user:create,delete,update:view");
//或者
subject.checkPermissions("system:user:*");  
subject.checkPermissions("system:user");  
  • 1
  • 2
  • 3
  • 4

通過“system:user:*”驗證“system:user:create,delete,update:view”可以,但是反過來是不成立的。

2.2.2.4.所有資源全部許可權

1.ini配置檔案

role61=*:view   
  • 1

2.然後通過如下程式碼判斷

subject.checkPermissions("user:view");  
  • 1

使用者擁有所有資源的“view”所有許可權。假設判斷的許可權是“"system:user:view”,那麼需要“role5=* ?:view”這樣寫才行。

2.2.2.5.單個例項單個許可權

1.ini配置檔案

role71=user:view:1  
  • 1

對資源user的1例項擁有view許可權。

2.然後通過如下程式碼判斷

subject.checkPermissions("user:view:1");  
  • 1

2.2.2.6.單個例項多個許可權

1.ini配置檔案

role72="user:update,delete:1" 
  • 1

2.然後通過如下程式碼判斷

subject.checkPermissions("user:delete,update:1");  
subject.checkPermissions("user:update:1", "user:delete:1"); 
  • 1
  • 2

2.2.2.7.單個例項所有許可權

1.ini配置檔案

role73=user:*:1 
  • 1

2.然後通過如下程式碼判斷

subject.checkPermissions("user:update:1", "user:delete:1", "user:view:1");  
  • 1

2.2.2.8.所有例項單個許可權

1.ini配置檔案

role74=user:auth:*
  • 1

2.然後通過如下程式碼判斷

subject.checkPermissions("user:auth:1", "user:auth:2");   
  • 1

2.2.2.9.所有例項所有許可權

1.ini配置檔案

role75=user:*:*  
  • 1

2.然後通過如下程式碼判斷

subject.checkPermissions("user:view:1", "user:auth:2");   
  • 1

2.2.2.10.Shiro對許可權字串缺失部分的處理

如“user:view”等價於“user:view:”;而“organization”等價於“organization:”或者“organization::”。可以這麼理解,這種方式實現了字首匹配。
另外如“user:”可以匹配如“user:delete”、“user:delete”可以匹配如“user:delete:1”、“user::1”可以匹配如“user:view:1”、“user”可以匹配“user:view”或“user:view:1”等。即可以匹配所有,不加可以進行字首匹配;但是如“:view”不能匹配“system:user:view”,需要使用“::view”,即字尾匹配必須指定字首(多個冒號就需要多個來匹配)。

2.2.2.11.效能問題

萬用字元匹配方式比字串相等匹配來說是更復雜的,因此需要花費更長時間,但是一般系統的許可權不會太多,且可以配合快取來提供其效能,如果這樣效能還達不到要求我們可以實現位操作演算法實現效能更好的許可權匹配。另外例項級別的許可權驗證如果資料量太大也不建議使用,可能造成查詢許可權及匹配變慢。可以考慮比如在sql查詢時加上許可權字串之類的方式在查詢時就完成了許可權匹配。

2.2.3.授權流程

流程如下:
1、首先呼叫Subject.isPermitted*/hasRole介面,其會委託給SecurityManager,而SecurityManager接著會委託給Authorizer;
2、Authorizer是真正的授權者,如果我們呼叫如isPermitted(“user:view”),其首先會通過PermissionResolver把字串轉換成相應的Permission例項;
3、在進行授權之前,其會呼叫相應的Realm獲取Subject相應的角色/許可權用於匹配傳入的角色/許可權;
4、Authorizer會判斷Realm的角色/許可權是否和傳入的匹配,如果有多個Realm,會委託給ModularRealmAuthorizer進行迴圈判斷,如果匹配如isPermitted
/hasRole*會返回true,否則返回false表示授權失敗。

ModularRealmAuthorizer進行多Realm匹配流程:
1、首先檢查相應的Realm是否實現了實現了Authorizer;
2、如果實現了Authorizer,那麼接著呼叫其相應的isPermitted*/hasRole*介面進行匹配;
3、如果有一個Realm匹配那麼將返回true,否則返回false。

如果Realm進行授權的話,應該繼承AuthorizingRealm,其流程是:
1.1、如果呼叫hasRole*,則直接獲取AuthorizationInfo.getRoles()與傳入的角色比較即可;
1.2、首先如果呼叫如isPermitted(“user:view”),首先通過PermissionResolver將許可權字串轉換成相應的Permission例項,預設使用WildcardPermissionResolver,即轉換為萬用字元的WildcardPermission;
2、通過AuthorizationInfo.getObjectPermissions()得到Permission例項集合;通過AuthorizationInfo. getStringPermissions()得到字串集合並通過PermissionResolver解析為Permission例項;然後獲取使用者的角色,並通過RolePermissionResolver解析角色對應的許可權集合(預設沒有實現,可以自己提供);
3、接著呼叫Permission. implies(Permission p)逐個與傳入的許可權比較,如果有匹配的則返回true,否則false。