Shiro 實戰教程(上)

1

注:該shiro教程來源於B站上的一個教程,由於源碼是付費的,我就不分享了,下篇講解springboot搭配shiro進行使用。

我的個人博客:

天涯志

我的公衆號:菜鳥小謝

8

1.權限的管理

1.1 什麼是權限管理

基本上涉及到用戶參與的系統都要進行權限管理,權限管理屬於系統安全的範疇,權限管理實現對用戶訪問系統的控制,按照安全規則或者安全策略控制用戶可以訪問而且只能訪問自己被授權的資源。

權限管理包括用戶身份認證授權兩部分,簡稱認證授權。對於需要訪問控制的資源用戶首先經過身份認證,認證通過後用戶具有該資源的訪問權限方可訪問。

1.2 什麼是身份認證

身份認證,就是判斷一個用戶是否爲合法用戶的處理過程。最常用的簡單身份認證方式是系統通過覈對用戶輸入的用戶名和口令,看其是否與系統中存儲的該用戶的用戶名和口令一致,來判斷用戶身份是否正確。對於採用指紋等系統,則出示指紋;對於硬件Key等刷卡系統,則需要刷卡。

1.3 什麼是授權

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


2.什麼是shiro

Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.

Shiro 是一個功能強大且易於使用的Java安全框架,它執行身份驗證、授權、加密和會話管理。使用Shiro易於理解的API,您可以快速輕鬆地保護任何應用程序—從最小的移動應用程序到最大的web和企業應用程序。

Shiro是apache旗下一個開源框架,它將軟件系統的安全認證相關的功能抽取出來,實現用戶身份認證,權限授權、加密、會話管理等功能,組成了一個通用的安全認證框架。


3.shiro的核心架構

2

3.1 Subject

Subject即主體,外部應用與subject進行交互,subject記錄了當前操作用戶,將用戶的概念理解爲當前操作的主體,可能是一個通過瀏覽器請求的用戶,也可能是一個運行的程序。 Subject在shiro中是一個接口,接口中定義了很多認證授相關的方法,外部程序通過subject進行認證授,而subject是通過SecurityManager安全管理器進行認證授權

3.2 SecurityManager

SecurityManager即安全管理器,對全部的subject進行安全管理,它是shiro的核心,負責對所有的subject進行安全管理。通過SecurityManager可以完成subject的認證、授權等,實質上SecurityManager是通過Authenticator進行認證,通過Authorizer進行授權,通過SessionManager進行會話管理等。

SecurityManager是一個接口,繼承了Authenticator, Authorizer, SessionManager這三個接口。

3.3 Authenticator

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

3.4 Authorizer

Authorizer即授權器,用戶通過認證器認證通過,在訪問功能時需要通過授權器判斷用戶是否有此功能的操作權限。

3.5 Realm

Realm即領域,相當於datasource數據源,securityManager進行安全認證需要通過Realm獲取用戶權限數據,比如:如果用戶身份數據在數據庫那麼realm就需要從數據庫獲取用戶身份信息。

  • ​ 注意:不要把realm理解成只是從數據源取數據,在realm中還有認證授權校驗的相關的代碼。

3.6 SessionManager

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

3.7 SessionDAO

SessionDAO即會話dao,是對session會話操作的一套接口,比如要將session存儲到數據庫,可以通過jdbc將會話存儲到數據庫。

3.8 CacheManager

CacheManager即緩存管理,將用戶權限數據存儲在緩存,這樣可以提高性能。

3.9 Cryptography

Cryptography即密碼管理,shiro提供了一套加密/解密的組件,方便開發。比如提供常用的散列、加/解密等功能。


4. shiro中的認證

4.1 認證

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

4.2 shiro中認證的關鍵對象

  • Subject:主體

訪問系統的用戶,主體可以是用戶、程序等,進行認證的都稱爲主體;

  • Principal:身份信息

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

  • credential:憑證信息

是隻有主體自己知道的安全信息,如密碼、證書等。

4.3 認證流程

3

4.4 認證的開發

1. 創建項目並引入依賴
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-core</artifactId>
  <version>1.5.3</version>
</dependency>
2. 引入shiro配置文件並加入如下配置
[users]
xiaoxie=123
zhangsan=456

4

3.開發認證代碼
public class TestAuthenticator {
    public static void main(String[] args) {
        //創建securityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        defaultSecurityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        //將安裝工具類中設置默認安全管理器
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //獲取主體對象
        Subject subject = SecurityUtils.getSubject();
        //創建token令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen1", "123");
        try {
            subject.login(token);//用戶登錄
            System.out.println("登錄成功~~");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用戶名錯誤!!");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密碼錯誤!!!");
        }

    }
}
  • DisabledAccountException(帳號被禁用)

  • LockedAccountException(帳號被鎖定)

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

  • ExpiredCredentialsException(憑證過期)等


4.5 自定義Realm

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

1.shiro提供的Realm

5

2.根據認證源碼認證使用的是SimpleAccountRealm

6
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
 */
public class CustomerRealm extends AuthorizingRealm {
    //認證方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    //授權方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String) token.getPrincipal();
        if("xiaochen".equals(principal)){
            return new SimpleAuthenticationInfo(principal,"123",this.getName());
        }
        return null;
    }
}
4.使用自定義Realm認證
public class TestAuthenticatorCusttomerRealm {
    public static void main(String[] args) {
        //創建securityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //IniRealm realm = new IniRealm("classpath:shiro.ini");
        //設置爲自定義realm獲取認證數據
        defaultSecurityManager.setRealm(new CustomerRealm());
        //將安裝工具類中設置默認安全管理器
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //獲取主體對象
        Subject subject = SecurityUtils.getSubject();
        //創建token令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        try {
            subject.login(token);//用戶登錄
            System.out.println("登錄成功~~");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用戶名錯誤!!");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密碼錯誤!!!");
        }

    }
}

4.6 使用MD5和Salt

實際應用是將鹽和散列後的值存在數據庫中,自動realm從數據庫取出鹽和加密後的值由shiro完成密碼校驗。

1.自定義md5+salt的realm
/**
 * 自定義md5+salt realm
 */
public class CustomerRealm extends AuthorizingRealm {
    //認證方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    //授權方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String) token.getPrincipal();
        if("xiaochen".equals(principal)){
            String password = "3c88b338102c1a343bcb88cd3878758e";
            String salt = "Q4F%";
            return new SimpleAuthenticationInfo(principal,password, 
                                                ByteSource.Util.bytes(salt),this.getName());
        }
        return null;
    }
2.使用md5 + salt 認證
public class TestAuthenticatorCusttomerRealm {
    public static void main(String[] args) {
        //創建securityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //IniRealm realm = new IniRealm("classpath:shiro.ini");
        //設置爲自定義realm獲取認證數據
        CustomerRealm customerRealm = new CustomerRealm();
        //設置md5加密
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("MD5");
        credentialsMatcher.setHashIterations(1024);//設置散列次數
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        defaultSecurityManager.setRealm(customerRealm);
        //將安裝工具類中設置默認安全管理器
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //獲取主體對象
        Subject subject = SecurityUtils.getSubject();
        //創建token令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        try {
            subject.login(token);//用戶登錄
            System.out.println("登錄成功~~");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用戶名錯誤!!");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密碼錯誤!!!");
        }

    }
}

5. shiro中的授權

5.1 授權

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

5.2 關鍵對象

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

Who,即主體(Subject),主體需要訪問系統中的資源。

What,即資源(Resource),如系統菜單、頁面、按鈕、類方法、系統商品信息等。資源包括資源類型資源實例,比如商品信息爲資源類型,類型爲t01的商品爲資源實例,編號爲001的商品信息也屬於資源實例。

How,權限/許可(Permission),規定了主體對資源的操作許可,權限離開資源沒有意義,如用戶查詢權限、用戶添加權限、某個類方法的調用權限、編號爲001用戶的修改權限等,通過權限可知主體對哪些資源都有哪些操作許可。

5.3 授權流程

7

5.4 授權方式

  • 基於角色的訪問控制

    • RBAC基於角色的訪問控制(Role-Based Access Control)是以角色爲中心進行訪問控制

      if(subject.hasRole("admin")){
         //操作什麼資源
      }
      
  • 基於資源的訪問控制

    • RBAC基於資源的訪問控制(Resource-Based Access Control)是以資源爲中心進行訪問控制

      if(subject.isPermission("user:update:01")){ //資源實例
        //對01用戶進行修改
      }
      if(subject.isPermission("user:update:*")){  //資源類型
        //對01用戶進行修改
      }
      

5.5 權限字符串

​ 權限字符串的規則是:資源標識符:操作:資源實例標識符,意思是對哪個資源的哪個實例具有什麼操作,“:”是資源/操作/實例的分割符,權限字符串也可以使用*通配符。

例子:

  • 用戶創建權限:user:create,或user:create:*
  • 用戶修改實例001的權限:user:update:001
  • 用戶實例001的所有權限:user:*:001

5.6 shiro中授權編程實現方式

  • 編程式

    Subject subject = SecurityUtils.getSubject();
    if(subject.hasRole(“admin”)) {
    	//有權限
    } else {
    	//無權限
    }
    
  • 註解式

    @RequiresRoles("admin")
    public void hello() {
    	//有權限
    }
    
  • 標籤式

    JSP/GSP 標籤:在JSP/GSP 頁面通過相應的標籤完成:
    <shiro:hasRole name="admin">
    	<!— 有權限—>
    </shiro:hasRole>
    注意: Thymeleaf 中使用shiro需要額外集成!
    

5.7 開發授權

1.realm的實現
public class CustomerRealm extends AuthorizingRealm {
    //認證方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String primaryPrincipal = (String) principals.getPrimaryPrincipal();
        System.out.println("primaryPrincipal = " + primaryPrincipal);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        simpleAuthorizationInfo.addRole("admin");

        simpleAuthorizationInfo.addStringPermission("user:update:*");
        simpleAuthorizationInfo.addStringPermission("product:*:*");


        return simpleAuthorizationInfo;
    }

    //授權方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String) token.getPrincipal();
        if("xiaochen".equals(principal)){
            String password = "3c88b338102c1a343bcb88cd3878758e";
            String salt = "Q4F%";
            return new SimpleAuthenticationInfo(principal,password, 
                                                ByteSource.Util.bytes(salt),this.getName());
        }
        return null;
    }

}
2.授權
public class TestAuthenticatorCusttomerRealm {
    public static void main(String[] args) {
        //創建securityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //IniRealm realm = new IniRealm("classpath:shiro.ini");
        //設置爲自定義realm獲取認證數據
        CustomerRealm customerRealm = new CustomerRealm();
        //設置md5加密
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("MD5");
        credentialsMatcher.setHashIterations(1024);//設置散列次數
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        defaultSecurityManager.setRealm(customerRealm);
        //將安裝工具類中設置默認安全管理器
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //獲取主體對象
        Subject subject = SecurityUtils.getSubject();
        //創建token令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        try {
            subject.login(token);//用戶登錄
            System.out.println("登錄成功~~");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用戶名錯誤!!");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密碼錯誤!!!");
        }
        //認證通過
        if(subject.isAuthenticated()){
            //基於角色權限管理
            boolean admin = subject.hasRole("admin");
            System.out.println(admin);

            boolean permitted = subject.isPermitted("product:create:001");
            System.out.println(permitted);
        }

    }
}

下一篇講解shiro搭配SpringBoot進行開發,謝謝!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章