天天看點

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

一、 權限概述

1. 什麼是權限

權限管理,一般指根據系統設定的安全政策或者安全規則,使用者可以通路而且隻能通路自己被授權的資源,不多不少。權限管理幾乎出現在任何系統裡面,隻要有使用者和密碼的系統。

權限管理在系統中一般分為:

  • 通路權限

    一般表示你能做什麼樣的操作,或者能夠通路那些資源。例如:給張三賦予“店鋪主管”角色,“店鋪主管”具有“查詢員工”、“添加員工”、“修改員工”和“删除員工”權限。此時張三能夠進入系統,則可以進行這些操作。

  • 資料權限

    一般表示某些資料是否屬于你,或者屬于你可以操作範圍。例如:張三是"店鋪主管"角色,他可以看他手下客服人員所有的服務的買家訂單資訊,他的手下隻能看自己負責的訂單資訊。

2. 認證概念

⑴ 什麼是認證

身份認證,就是判斷一個使用者是否為合法使用者的處理過程。最常用的簡單身份認證方式是系統通過核對使用者輸入的使用者名和密碼,看其是否與系統中存儲的該使用者的使用者名和密碼一緻,來判斷使用者身份是否正确。例如:密碼登入,手機短信驗證、三方授權等。

⑵ 認證流程

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

⑶ 關鍵對象

上邊的流程圖中需要了解以下關鍵對象:

Subject:主體:通路系統的使用者,主體可以是使用者、程式等,進行認證的都稱為主體;

Principal:身份資訊是主體(subject)進行身份認證的辨別,辨別必須具有唯一性,如使用者名、手機号、郵箱位址等,一個主體可以有多個身份,但是必須有一個主身份(Primary Principal)。

credential:憑證資訊:是隻有主體自己知道的安全資訊,如密碼、證書等。

3. 授權概念

⑴ 什麼是授權

授權,即通路控制,控制誰能通路哪些資源。主體進行身份認證後,系統會為其配置設定對應的權限,當通路資源時,會校驗其是否有通路此資源的權限。

這裡首先了解4個對象。

  • 使用者對象user:目前操作的使用者、程式。
  • 資源對象resource:目前被通路的對象
  • 角色對象role :一組 “權限操作許可權” 的集合。
  • 權限對象permission:權限操作許可權

⑵ 授權流程

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

⑶ 關鍵對象

授權可簡單了解為who對what進行How操作

Who: 主體(Subject),可以是一個使用者、也可以是一個程式

What: 資源(Resource),如系統菜單、頁面、按鈕、方法、系統商品資訊等。

    通路類型:商品菜單,訂單菜單、分銷商菜單

    資料類型:我的商品,我的訂單,我的評價

How: 權限/許可(Permission)

    我的商品(資源)—>通路我的商品(權限許可)

    分銷商菜單(資源)----> 通路分銷商清單(權限許可)

二、 Shiro概述

1. Shiro簡介

⑴ 什麼是Shiro?

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

⑵ Shiro 的特點

Shiro 是一個強大而靈活的開源安全架構,能夠非常清晰的處理認證、授權、管理會話以及密碼加密。如下是它所具有的特點:

  • 易于了解的 Java Security API;
  • 簡單的身份認證(登入),支援多種資料源(LDAP,JDBC 等);
  • 對角色的簡單的簽權(通路控制),也支援細粒度的鑒權;
  • 支援一級緩存,以提升應用程式的性能;
  • 内置的基于 POJO 企業會話管理,适用于 Web 以及非 Web 的環境;
  • 異構用戶端會話通路;
  • 非常簡單的加密 API;
  • 不跟任何的架構或者容器捆綁,可以獨立運作。

⑶ 核心元件

1) Shiro架構圖

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

2) Subject

Subject主體,外部應用與subject進行互動,subject将使用者作為目前操作的主體,這個主體:可以是一個通過浏覽器請求的使用者,也可能是一個運作的程式。Subject在shiro中是一個接口,接口中定義了很多認證授相關的方法,外部程式通過subject進行認證授,而subject是通過SecurityManager安全管理器進行認證授權。

3) SecurityManager

SecurityManager權限管理器,它是shiro的核心,負責對所有的subject進行安全管理。通過SecurityManager可以完成subject的認證、授權等,SecurityManager是通過Authenticator進行認證,通過Authorizer進行授權,通過SessionManager進行會話管理等。SecurityManager是一個接口,繼承了Authenticator, Authorizer, SessionManager這三個接口。

4) Authenticator

Authenticator即認證器,對使用者登入時進行身份認證

5) Authorizer

Authorizer授權器,使用者通過認證器認證通過,在通路功能時需要通過授權器判斷使用者是否有此功能的操作權限。

6) Realm(資料庫讀取+認證功能+授權功能實作)

Realm領域,相當于datasource資料源,securityManager進行安全認證需要通過Realm擷取使用者權限資料

比如:

  如果使用者身份資料在資料庫那麼realm就需要從資料庫擷取使用者身份資訊。

注意:

  不要把realm了解成隻是從資料源取資料,在realm中還有認證授權校驗的相關的代碼。

7) SessionManager

SessionManager會話管理,shiro架構定義了一套會話管理,它不依賴web容器的session,是以shiro可以使用在非web應用上,也可以将分布式應用的會話集中在一點管理,此特性可使它實作單點登入。

8) SessionDAO

SessionDAO即會話dao,是對session會話操作的一套接口

比如:

  可以通過jdbc将會話存儲到資料庫

  也可以把session存儲到緩存伺服器

9) CacheManager

CacheManager緩存管理,将使用者權限資料存儲在緩存,這樣可以提高性能

10) Cryptography

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

三、 Shiro入門

1. 身份認證

⑴ 基本流程

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

流程如下:

​ 1、Shiro把使用者的資料封裝成辨別token,token一般封裝着使用者名,密碼等資訊

​ 2、使用Subject門面擷取到封裝着使用者的資料的辨別token

​ 3、Subject把辨別token交給SecurityManager,在SecurityManager安全中心中,SecurityManager把辨別token委托給認證器Authenticator進行身份驗證。認證器的作用一般是用來指定如何驗證,它規定本次認證用到哪些Realm

​ 4、認證器Authenticator将傳入的辨別token,與資料源Realm對比,驗證token是否合法

⑵ 案例示範

1) 需求

使用shiro完成一個使用者的登入

2) 實作

① 建立項目

shiro-study-project

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
② 導入依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kejizhentan</groupId>
    <artifactId>shiro-study-project</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- compiler插件, 設定JDK版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
           
③ 編寫shiro.ini
#聲明使用者賬号
[users]
zhangsan=123456
           
④ 編寫HelloShiro
package com.kejizhentan;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;

public class HelloShiro {
    public static void main(String[] args) {
        //導入權限ini檔案建構權限工廠
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //工廠建構安全管理器
        SecurityManager securityManager = factory.getInstance();
        //使用SecurityUtils工具生效安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //使用SecurityUtils工具獲得主體
        Subject subject = SecurityUtils.getSubject();
        //建構賬号token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "123456");
        //登入操作
        subject.login(usernamePasswordToken);
        System.out.println("是否登入成功:" + subject.isAuthenticated());
    }
}
           
⑤ 測試
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

3) 小結

1、權限定義:ini檔案

2、加載過程:

  導入權限ini檔案建構權限工廠 工廠建構安全管理器

  使用SecurityUtils工具生效安全管理器 使用SecurityUtils工具獲得主體

  使建構賬号token用SecurityUtils工具獲得主體 建構賬号token 登入操作

2. Realm

⑴ Realm接口

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

是以,一般在真實的項目中,我們不會直接實作Realm接口,我們一般的情況就是直接繼承AuthorizingRealm,能夠繼承到認證與授權功能。它需要強制重寫兩個方法

public class DefinitionRealm extends AuthorizingRealm {
 
    /**
	 * @Description 認證
	 * @param authcToken token對象
	 * @return 
	 */
	public abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
        return null;
    }

	/**
	 * @Description 鑒權
	 * @param principals 令牌
	 * @return
	 */
	public abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
        return null;
    }
}
           

⑵ 自定義Realm

1) 需求

定義Realm,取得密碼用于比較

2) 實作

① 建立項目

shiro-study-project

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
② 定義SecurityService

SecurityService

/**
 * @Description:權限服務接口
 */
public interface SecurityService {

    /**
     * @Description 查找密碼按使用者登入名
     * @param loginName 登入名稱
     * @return
     */
    String findPasswordByLoginName(String loginName);
}
           

SecurityServiceImpl

public class SecurityServiceImpl implements SecurityService {
    @Override
    public String findPasswordByLoginName(String loginName) {
        return "123456";
    }
}
           
③ 定義DefinitionRealm
package com.kejizhentan.realm;

import com.kejizhentan.service.SecurityService;
import com.kejizhentan.service.impl.SecurityServiceImpl;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class DefinitionRealm extends AuthorizingRealm {
    /**
     * @Description 認證接口
     * @param token 傳遞登入token
     * @return
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //從AuthenticationToken中獲得登入名稱
        String loginName = (String) token.getPrincipal();
        SecurityService securityService = new SecurityServiceImpl();
        String password = securityService.findPasswordByLoginName(loginName);
        if ("".equals(password)||password==null){
            throw new UnknownAccountException("賬戶不存在");
        }
        //傳遞賬号和密碼
        return  new SimpleAuthenticationInfo(loginName,password,getName());
    }


    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
}

           
④ 自定義模拟Controller的類
package com.kejizhentan.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
/**
 * @Auther: kejizhentan
 * @Date 2022/12/30 23:14
 * @Description: 模拟controller
 */
public class ShiroController {
    public static void main(String[] args) {
        //導入權限ini檔案建構權限工廠
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //工廠建構安全管理器
        SecurityManager securityManager = factory.getInstance();
        //使用SecurityUtils工具生效安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //使用SecurityUtils工具獲得主體
        Subject subject = SecurityUtils.getSubject();
        //建構賬号token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "123456");
        //登入操作
        subject.login(usernamePasswordToken);
        System.out.println("是否登入成功:" + subject.isAuthenticated());
    }
}
           
⑤ 編輯shiro.ini
#聲明自定義的realm,且為安全管理器指定realms
[main]
definitionRealm=com.kejizhentan.realm.DefinitionRealm
securityManager.realms=$definitionRealm
           
⑥ 測試
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

3) 認證源碼跟蹤

(1)通過debug模式追蹤源碼subject.login(token) 發現。首先是進入Subject接口的預設實作類。果然,Subject将使用者的使用者名密碼委托給了securityManager去做。

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(2)然後,securityManager說:“卧槽,認證器authenticator小弟,聽說你的大學學的專業就是認證呀,那麼這個認證的任務就交給你咯”。遂将使用者的token委托給内部認證元件authenticator去做

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(3)事實上,securityManager的内部元件一個比一個懶。内部認證元件authenticator說:“你們傳過來的token我需要拿去跟資料源Realm做對比,這樣吧,這個光榮的任務就交給Realm你去做吧”。Realm對象:“一群大懶蟲!”。

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(4)Realm在接到内部認證元件authenticator元件後很傷心,最後對電腦前的你說:“大兄弟,對不住了,你去實作一下呗”。從圖中的方法體中可以看到,目前對象是Realm類對象,即将調用的方法是doGetAuthenticationInfo(token)。而這個方法,就是你即将要重寫的方法。如果帳号密碼通過了,那麼傳回一個認證成功的info憑證。如果認證失敗,抛出一個異常就好了。你說:“什麼?最終還是勞資來認證?”沒錯,就是苦逼的你去實作了,誰叫你是程式猿呢。是以,你不得不查詢一下資料庫,重寫doGetAuthenticationInfo方法,查出來正确的帳号密碼,傳回一個正确的憑證info

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(5)好了,這個時候你自己編寫了一個類,繼承了AuthorizingRealm,并實作了上述doGetAuthenticationInfo方法。你在doGetAuthenticationInfo中編寫了查詢資料庫的代碼,并将資料庫中存放的使用者名與密碼封裝成了一個AuthenticationInfo對象傳回。可以看到下圖中,info這個對象是有值的,說明從資料庫中查詢出來了正确的帳号密碼

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(6)那麼,接下來就很簡單了。把使用者輸入的帳号密碼與剛才你從資料庫中查出來的帳号密碼對比一下即可。token封裝着使用者的帳号密碼,AuthenticationInfo封裝着從資料庫中查詢出來的帳号密碼。再往下追蹤一下代碼,最終到了下圖中的核心區域。如果沒有報異常,說明本次登入成功。

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

3. 編碼、雜湊演算法

⑴ 編碼與解碼

Shiro提供了base64和16進制字元串編碼/解碼的API支援,友善一些編碼解碼操作。

Shiro内部的一些資料的【存儲/表示】都使用了base64和16進制字元串

1) 需求

了解base64和16進制字元串編碼/解碼

2) 建立項目

encode-decode-project

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

3)添加pom依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kejizhentan</groupId>
    <artifactId>encode-decode-project</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- compiler插件, 設定JDK版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
           

4)建立EncodesUtil

package com.kejizhentan.utils;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.Hex;

/**
 * @Description:封裝base64和16進制編碼解碼工具類
 */
public class EncodesUtil {

    /**
     * @Description HEX-byte[]--String轉換
     * @param input 輸入數組
     * @return String
     */
    public static String encodeHex(byte[] input){
        return Hex.encodeToString(input);
    }

    /**
     * @Description HEX-String--byte[]轉換
     * @param input 輸入字元串
     * @return byte數組
     */
    public static byte[] decodeHex(String input){
        return Hex.decode(input);
    }

    /**
     * @Description Base64-byte[]--String轉換
     * @param input 輸入數組
     * @return String
     */
    public static String encodeBase64(byte[] input){
        return Base64.encodeToString(input);
    }

    /**
     * @Description Base64-String--byte[]轉換
     * @param input 輸入字元串
     * @return byte數組
     */
    public static byte[] decodeBase64(String input){
        return Base64.decode(input);
    }

}
           

5)建立ClientTest

package com.kejizhentan.test;

import com.kejizhentan.utils.EncodesUtil;
import org.junit.Test;

/**
 * @Description:測試
 */
public class ClientTest {

    /**
     * @Description 測試16進制編碼
     */
    @Test
    public void testHex(){
        //自定義字元串
        String val = "hello";
        //先将字元串轉換成位元組數組,然後通過工具類将位元組數組編碼成字元串
        String flag = EncodesUtil.encodeHex(val.getBytes());
        //再通過工具将編碼的字元串解碼成位元組資料,然後再裝換成字元串
        String valHandler = new String(EncodesUtil.decodeHex(flag));
        //比較經編碼和解碼的字元串是否和原先的字元串一樣
        System.out.println("比較結果:"+val.equals(valHandler));
    }

    /**
     * @Description 測試base64編碼
     */
    @Test
    public void testBase64(){
        //自定義字元串
        String val = "hello";
        //先将字元串轉換成位元組數組,然後通過工具類将位元組數組編碼成字元串
        String flag = EncodesUtil.encodeBase64(val.getBytes());
        //再通過工具将編碼的字元串解碼成位元組資料,然後再裝換成字元串
        String valHandler = new String(EncodesUtil.decodeBase64(flag));
        //比較經編碼和解碼的字元串是否和原先的字元串一樣
        System.out.println("比較結果:"+val.equals(valHandler));
    }
}
           

6) 小結

1、shiro目前支援的編碼與解碼:

  base64

  (HEX)16進制字元串

2、那麼shiro的編碼與解碼什麼時候使用呢?又是怎麼使用的呢?

⑵ 雜湊演算法

雜湊演算法一般用于生成資料的摘要資訊,是一種不可逆的算法,一般适合存儲密碼之類的資料,常見的雜湊演算法如MD5、SHA等。一般進行散列時最好提供一個salt(鹽),比如加密密碼“admin”,産生的散列值是“21232f297a57a5a743894a0e4a801fc3”,可以到一些md5解密網站很容易的通過散列值得到密碼“admin”,即如果直接對密碼進行散列相對來說破解更容易,此時我們可以加一些隻有系統知道的幹擾資料,如salt(即鹽);這樣散列的對象是“密碼+salt”,這樣生成的散列值相對來說更難破解。

shiro支援的雜湊演算法:

Md2Hash、Md5Hash、Sha1Hash、Sha256Hash、Sha384Hash、Sha512Hash

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

⑶ 在原來的encode-decode-project項目上建立HashUtil工具類

package com.kejizhentan.utils;

import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;

import java.util.HashMap;
import java.util.Map;
/**
 * @Auther: kejizhentan
 * @Date 2022/12/31 14:20
 * @Description: 雜湊演算法加密工具類
 */
public class HashUtil {
    private static final String SHA1 = "SHA-1";

    private static final Integer ITERATIONS =512;

    /**
     * @Description sha1方法
     * @param input 需要散列字元串
     * @param salt 鹽字元串
     * @return
     */
    public static String sha1(String input, String salt) {
        return new SimpleHash(SHA1, input, salt,ITERATIONS).toString();
    }

    /**
     * @Description 随機獲得salt字元串
     * @return
     */
    public static String generateSalt(){
        SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
        return randomNumberGenerator.nextBytes().toHex();
    }


    /**
     * @Description 生成密碼字元密文和salt密文
     * @param
     * @return
     */
    public static Map<String,String> entryptPassword(String passwordPlain) {
        Map<String,String> map = new HashMap<>();
        String salt = generateSalt();
        String password =sha1(passwordPlain,salt);
        map.put("salt", salt);
        map.put("password", password);
        return map;
    }
}
           

⑷ 修改測試類ClientTest

package com.kejizhentan.test;

import com.kejizhentan.utils.EncodesUtil;
import com.kejizhentan.utils.HashUtil;
import org.junit.Test;

import java.util.Map;

/**
 * @Description:測試
 */
public class ClientTest {

    /**
     * @Description 測試16進制編碼
     */
    @Test
    public void testHex(){
        //自定義字元串
        String val = "hello";
        //先将字元串轉換成位元組數組,然後通過工具類将位元組數組編碼成字元串
        String flag = EncodesUtil.encodeHex(val.getBytes());
        //再通過工具将編碼的字元串解碼成位元組資料,然後再裝換成字元串
        String valHandler = new String(EncodesUtil.decodeHex(flag));
        //比較經編碼和解碼的字元串是否和原先的字元串一樣
        System.out.println("比較結果:"+val.equals(valHandler));
    }

    /**
     * @Description 測試base64編碼
     */
    @Test
    public void testBase64(){
        //自定義字元串
        String val = "hello";
        //先将字元串轉換成位元組數組,然後通過工具類将位元組數組編碼成字元串
        String flag = EncodesUtil.encodeBase64(val.getBytes());
        //再通過工具将編碼的字元串解碼成位元組資料,然後再裝換成字元串
        String valHandler = new String(EncodesUtil.decodeBase64(flag));
        //比較經編碼和解碼的字元串是否和原先的字元串一樣
        System.out.println("比較結果:"+val.equals(valHandler));
    }

    /**
     * @Auther: kejizhentan
     * @Date 2022/12/31 14:25
     * @Description: 雜湊演算法加密
     */
    @Test
    public void testDigestsUtil(){
        Map<String,String> map =  HashUtil.entryptPassword("123456");
        System.out.println("獲得結果:"+map.toString());
    }

}
           

4. Realm使用雜湊演算法

上面我們了解編碼,以及雜湊演算法,那麼在realm中怎麼使用?在shiro-study-project中我們使用的密碼是明文的校驗方式,也就是SecurityServiceImpl中findPasswordByLoginName傳回的是明文123456的密碼

public class SecurityServiceImpl implements SecurityService {
    @Override
    public String findPasswordByLoginName(String loginName) {
        return "123456";
    }
}
           

⑴ 建立項目

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

⑵ 添加pom依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kejizhentan</groupId>
    <artifactId>shiro-ciphertext-realm-project</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- compiler插件, 設定JDK版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
           

⑶ 添加shiro.ini

#聲明自定義的realm,且為安全管理器指定realms
[main]
definitionRealm=com.kejizhentan.realm.DefinitionRealm
securityManager.realms=$definitionRealm
           

⑷ 添加EncodesUtil.java

package com.kejizhentan.utils;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.Hex;

/**
 * @Description:封裝base64和16進制編碼解碼工具類
 */
public class EncodesUtil {

    /**
     * @Description HEX-byte[]--String轉換
     * @param input 輸入數組
     * @return String
     */
    public static String encodeHex(byte[] input){
        return Hex.encodeToString(input);
    }

    /**
     * @Description HEX-String--byte[]轉換
     * @param input 輸入字元串
     * @return byte數組
     */
    public static byte[] decodeHex(String input){
        return Hex.decode(input);
    }

    /**
     * @Description Base64-byte[]--String轉換
     * @param input 輸入數組
     * @return String
     */
    public static String encodeBase64(byte[] input){
        return Base64.encodeToString(input);
    }

    /**
     * @Description Base64-String--byte[]轉換
     * @param input 輸入字元串
     * @return byte數組
     */
    public static byte[] decodeBase64(String input){
        return Base64.decode(input);
    }

}
           

⑸ 添加HashUtil.java

package com.kejizhentan.utils;

import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;

import java.util.HashMap;
import java.util.Map;

/**
 * @Auther: kejizhentan
 * @Date 2022/12/31 14:20
 * @Description: 雜湊演算法加密工具類
 */
public class HashUtil {
    public static final String SHA1 = "SHA-1";

    public static final Integer ITERATIONS =512;

    /**
     * @Description sha1方法
     * @param input 需要散列字元串
     * @param salt 鹽字元串
     * @return
     */
    public static String sha1(String input, String salt) {
        return new SimpleHash(SHA1, input, salt,ITERATIONS).toString();
    }

    /**
     * @Description 随機獲得salt字元串
     * @return
     */
    public static String generateSalt(){
        SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
        return randomNumberGenerator.nextBytes().toHex();
    }


    /**
     * @Description 生成密碼字元密文和salt密文
     * @param
     * @return
     */
    public static Map<String,String> entryptPassword(String passwordPlain) {
        Map<String,String> map = new HashMap<>();
        //随機獲得salt字元串
        String salt = generateSalt();
        //通過加鹽的方式生成加密後的密碼
        String password =sha1(passwordPlain,salt);
        map.put("salt", salt);
        map.put("password", password);
        return map;
    }
}
           

⑹ 修改SecurityService

SecurityService修改成傳回salt和password的map

package com.kejizhentan.service;

import java.util.Map;

/**
 * @Description:權限服務接口
 */
public interface SecurityService {

    /**
     * @Description 查找密碼按使用者登入名
     * @param loginName 登入名稱
     * @return
     */
    Map<String,String> findPasswordByLoginName(String loginName);
}
           
package com.kejizhentan.service.impl;

import com.kejizhentan.service.SecurityService;
import com.kejizhentan.utils.HashUtil;

import java.util.Map;

public class SecurityServiceImpl implements SecurityService {
    @Override
    public Map<String,String> findPasswordByLoginName(String loginName) {
        //模拟資料庫中存儲的密文資訊
        return  HashUtil.entryptPassword("123456");
    }
}
           

⑺ 添加DefinitionRealm.java

package com.kejizhentan.realm;

import com.kejizhentan.service.SecurityService;
import com.kejizhentan.service.impl.SecurityServiceImpl;
import com.kejizhentan.utils.HashUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import java.util.Map;

public class DefinitionRealm extends AuthorizingRealm {
    /**
     * @Description 構造函數
     */
    public DefinitionRealm() {
        //指定密碼比對方式為sha1
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(HashUtil.SHA1);
        //指定密碼疊代次數
        matcher.setHashIterations(HashUtil.ITERATIONS);
        //使用父親方法使比對方式生效
        setCredentialsMatcher(matcher);
    }
    /**
     * @Description 認證接口
     * @param token 傳遞登入token
     * @return
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //從AuthenticationToken中獲得登入名稱
        String loginName = (String) token.getPrincipal();
        SecurityService securityService = new SecurityServiceImpl();
        Map<String, String> map = securityService.findPasswordByLoginName(loginName);
        if (map.isEmpty()){
            throw new UnknownAccountException("賬戶不存在");
        }
        String salt = map.get("salt");
        String password = map.get("password");
        //傳遞賬号和密碼:參數1:緩存對象,參數2:密文密碼,參數三:位元組salt,參數4:目前DefinitionRealm名稱
        return  new SimpleAuthenticationInfo(loginName,password, ByteSource.Util.bytes(salt),getName());
    }


    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
}
           

⑻ 添加ShiroController.java

package com.kejizhentan.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;

/**
 * @Auther: kejizhentan
 * @Date 2022/12/30 23:14
 * @Description: 模拟controller
 */
public class ShiroController {
    public static void main(String[] args) {
        //導入權限ini檔案建構權限工廠
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //工廠建構安全管理器
        SecurityManager securityManager = factory.getInstance();
        //使用SecurityUtils工具生效安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //使用SecurityUtils工具獲得主體
        Subject subject = SecurityUtils.getSubject();
        //建構賬号token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "123456");
        //登入操作
        subject.login(usernamePasswordToken);
        System.out.println("是否登入成功:" + subject.isAuthenticated());
    }
}
           

⑼ 測試

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

5. 身份授權

⑴ 基本流程

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

1、首先調用Subject.isPermitted/hasRole接口,其會委托給SecurityManager。

2、SecurityManager接着會委托給内部元件Authorizer;

3、Authorizer再将其請求委托給我們的Realm去做;Realm才是真正幹活的;

4、Realm将使用者請求的參數封裝成權限對象。再從我們重寫的doGetAuthorizationInfo方法中擷取從資料庫中查詢到的權限集合。

5、Realm将使用者傳入的權限對象,與從資料庫中查出來的權限對象,進行一一對比。如果使用者傳入的權限對象在從資料庫中查出來的權限對象中,則傳回true,否則傳回false。

進行授權操作的前提:使用者必須通過認證。

在真實的項目中,角色與權限都存放在資料庫中。為了快速上手,我們先建立一個自定義DefinitionRealm,模拟它已經登入成功。直接傳回一個登入驗證憑證,告訴Shiro架構,我們從資料庫中查詢出來的密碼是也是就是你輸入的密碼。是以,不管使用者輸入什麼,本次登入驗證都是通過的。

/**
  * @Description 認證接口
  * @param token 傳遞登入token
  * @return
  */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    //從AuthenticationToken中獲得登入名稱
    String loginName = (String) token.getPrincipal();
    SecurityService securityService = new SecurityServiceImpl();
    Map<String, String> map = securityService.findPasswordByLoginName(loginName);
    if (map.isEmpty()){
        throw new UnknownAccountException("賬戶不存在");
    }
    String salt = map.get("salt");
    String password = map.get("password");
    //傳遞賬号和密碼:參數1:使用者認證憑證資訊,參數2:明文密碼,參數三:位元組salt,參數4:目前DefinitionRealm名稱
    return  new SimpleAuthenticationInfo(loginName,password, ByteSource.Util.bytes(salt),getName());
}
           

好了,接下來,我們要重寫我們本小節的核心方法了。在DefinitionRealm中找到下列方法:

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    return null;
}
           

此方法的傳入的參數PrincipalCollection principals,是一個包裝對象,它表示"使用者認證憑證資訊"。包裝的是誰呢?沒錯,就是認證doGetAuthenticationInfo()方法的傳回值的第一個參數loginName。你可以通過這個包裝對象的getPrimaryPrincipal()方法拿到此值,然後再從資料庫中拿到對應的角色和資源,建構SimpleAuthorizationInfo。

/**
 * @Description 授權方法
 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    //拿到使用者認證憑證資訊
    String loginName = (String) principals.getPrimaryPrincipal();
    //從資料庫中查詢對應的角色和資源
    SecurityService securityService = new SecurityServiceImpl();
    List<String> roles = securityService.findRoleByloginName(loginName);
    List<String> permissions = securityService.findPermissionByloginName(loginName);
    //建構資源校驗
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    authorizationInfo.addRoles(roles);
    authorizationInfo.addStringPermissions(permissions);
    return authorizationInfo;
}
           

⑵ 案例示範

1) 需求

1、實作doGetAuthorizationInfo方法實作鑒權
2、使用subject類實作權限的校驗
           

2)實作

① 建立項目

建立shiro-authentication-realm-project

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
② 導入pom依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kejizhentan</groupId>
    <artifactId>shiro-authentication-realm-project</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- compiler插件, 設定JDK版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
           
③ 建立shiro.ini
#聲明自定義的realm,且為安全管理器指定realms
[main]
definitionRealm=com.kejizhentan.realm.DefinitionRealm
securityManager.realms=$definitionRealm
           
④ 添加EncodesUtil.java
package com.kejizhentan.utils;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.Hex;

/**
 * @Description:封裝base64和16進制編碼解碼工具類
 */
public class EncodesUtil {

    /**
     * @Description HEX-byte[]--String轉換
     * @param input 輸入數組
     * @return String
     */
    public static String encodeHex(byte[] input){
        return Hex.encodeToString(input);
    }

    /**
     * @Description HEX-String--byte[]轉換
     * @param input 輸入字元串
     * @return byte數組
     */
    public static byte[] decodeHex(String input){
        return Hex.decode(input);
    }

    /**
     * @Description Base64-byte[]--String轉換
     * @param input 輸入數組
     * @return String
     */
    public static String encodeBase64(byte[] input){
        return Base64.encodeToString(input);
    }

    /**
     * @Description Base64-String--byte[]轉換
     * @param input 輸入字元串
     * @return byte數組
     */
    public static byte[] decodeBase64(String input){
        return Base64.decode(input);
    }

}
           
⑤ 添加HashUtil.java
package com.kejizhentan.utils;

import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;

import java.util.HashMap;
import java.util.Map;

/**
 * @Auther: kejizhentan
 * @Date 2022/12/31 14:20
 * @Description: 雜湊演算法加密工具類
 */
public class HashUtil {
    public static final String SHA1 = "SHA-1";

    public static final Integer ITERATIONS =512;

    /**
     * @Description sha1方法
     * @param input 需要散列字元串
     * @param salt 鹽字元串
     * @return
     */
    public static String sha1(String input, String salt) {
        return new SimpleHash(SHA1, input, salt,ITERATIONS).toString();
    }

    /**
     * @Description 随機獲得salt字元串
     * @return
     */
    public static String generateSalt(){
        SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
        return randomNumberGenerator.nextBytes().toHex();
    }


    /**
     * @Description 生成密碼字元密文和salt密文
     * @param
     * @return
     */
    public static Map<String,String> entryptPassword(String passwordPlain) {
        Map<String,String> map = new HashMap<>();
        //随機獲得salt字元串
        String salt = generateSalt();
        //通過加鹽的方式生成加密後的密碼
        String password =sha1(passwordPlain,salt);
        map.put("salt", salt);
        map.put("password", password);
        return map;
    }
}
           
⑥ 建立DefinitionRealm.java
package com.kejizhentan.realm;

import com.kejizhentan.service.SecurityService;
import com.kejizhentan.service.impl.SecurityServiceImpl;
import com.kejizhentan.utils.HashUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import java.util.List;
import java.util.Map;

public class DefinitionRealm extends AuthorizingRealm {
    /**
     * @Description 構造函數
     */
    public DefinitionRealm() {
        //指定密碼比對方式為sha1
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(HashUtil.SHA1);
        //指定密碼疊代次數
        matcher.setHashIterations(HashUtil.ITERATIONS);
        //使用父親方法使比對方式生效
        setCredentialsMatcher(matcher);
    }
    /**
     * @Description 認證接口
     * @param token 傳遞登入token
     * @return
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //從AuthenticationToken中獲得登入名稱
        String loginName = (String) token.getPrincipal();
        SecurityService securityService = new SecurityServiceImpl();
        Map<String, String> map = securityService.findPasswordByLoginName(loginName);
        if (map.isEmpty()){
            throw new UnknownAccountException("賬戶不存在");
        }
        String salt = map.get("salt");
        String password = map.get("password");
        //傳遞賬号和密碼:參數1:緩存對象,參數2:密文密碼,參數三:位元組salt,參數4:目前DefinitionRealm名稱
        return  new SimpleAuthenticationInfo(loginName,password, ByteSource.Util.bytes(salt),getName());
    }


    /**
     * @Description 授權方法
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //拿到使用者認證憑證資訊
        String loginName = (String) principals.getPrimaryPrincipal();
        //從資料庫中查詢對應的角色和資源
        SecurityService securityService = new SecurityServiceImpl();
        List<String> roles = securityService.findRoleByloginName(loginName);
        List<String> permissions = securityService.findPermissionByloginName(loginName);
        //建構資源校驗
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRoles(roles);
        authorizationInfo.addStringPermissions(permissions);
        return authorizationInfo;
    }
}
           
⑦ 建立SecurityService.java

SecurityService.java

/**
 * @Description:權限服務接口
 */
public interface SecurityService {

    /**
     * @Description 查找角色按使用者登入名
     * @param  loginName 登入名稱
     * @return
     */
    List<String> findRoleByloginName(String loginName);

    /**
     * @Description 查找資源按使用者登入名
     * @param  loginName 登入名稱
     * @return
     */
    List<String>  findPermissionByloginName(String loginName);

    /**
     * @Description 查找密碼按使用者登入名
     * @param loginName 登入名稱
     * @return
     */
    Map<String,String> findPasswordByLoginName(String loginName);
}
           

SecurityServiceImpl.java

package com.kejizhentan.service.impl;

import com.kejizhentan.service.SecurityService;
import com.kejizhentan.utils.HashUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class SecurityServiceImpl implements SecurityService {
    @Override
    public List<String> findRoleByloginName(String loginName) {
        List<String> list = new ArrayList<>();
        list.add("admin");
        list.add("dev");
        return list;
    }

    @Override
    public List<String>  findPermissionByloginName(String loginName) {
        List<String> list = new ArrayList<>();
        list.add("order:add");
        list.add("order:list");
        list.add("order:del");
        return list;
    }

    @Override
    public Map<String,String> findPasswordByLoginName(String loginName) {
        //模拟資料庫中存儲的密文資訊
        return  HashUtil.entryptPassword("123456");
    }
}
           
⑧ 建立ShiroController.java
package com.kejizhentan.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;

/**
 * @Auther: kejizhentan
 * @Date 2022/12/30 23:14
 * @Description: 模拟controller
 */
public class ShiroController {
    public static void main(String[] args) {
        Subject subject = shiroLogin("zhangsan", "123456");
        //判斷使用者是否已經登入
        System.out.println("是否登入成功:" + subject.isAuthenticated());

        //---------檢查目前使用者的角色資訊------------
        System.out.println("是否有管理者角色:"+subject.hasRole("admin"));
        //---------如果目前使用者有此角色,無傳回值。若沒有此權限,則抛 UnauthorizedException------------
        try {
            subject.checkRole("coder");
            System.out.println("有coder角色");
        }catch (Exception e){
            System.out.println("沒有coder角色");
        }

        //---------檢查目前使用者的權限資訊------------
        System.out.println("是否有檢視訂單清單資源:"+subject.isPermitted("order:list"));
        //---------如果目前使用者有此權限,無傳回值。若沒有此權限,則抛 UnauthorizedException------------
        try {
            subject.checkPermissions("order:add", "order:del");
            System.out.println("有添加和删除訂單資源");
        }catch (Exception e){
            System.out.println("沒有有添加和删除訂單資源");
        }
    }

    /**
     * @Description 登入方法
     */
    private static Subject shiroLogin(String loginName,String password) {
        //導入權限ini檔案建構權限工廠
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //工廠建構安全管理器
        SecurityManager securityManager = factory.getInstance();
        //使用SecurityUtils工具生效安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //使用SecurityUtils工具獲得主體
        Subject subject = SecurityUtils.getSubject();
        //建構賬号token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginName, password);
        //登入操作
        subject.login(usernamePasswordToken);
        return subject;
    }

}
           

3)授權源碼追蹤

(1)用戶端調用 subject.hasRole(“admin”),判斷目前使用者是否有"admin"角色權限。

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(2)Subject門面對象接收到要被驗證的角色資訊"admin",并将其委托給securityManager中驗證。

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(3)securityManager将驗證請求再次委托給内部的小弟:内部元件Authorizer authorizer

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(4)内部小弟authorizer也是個混子,将其委托給了我們自定義的Realm去做

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(5) 先拿到PrincipalCollection principal對象,同時傳入校驗的角色循環校驗,循環中先建立鑒權資訊

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(6)先看緩存中是否已經有鑒權資訊

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(7)都是一群懶貨!!最後幹活的還是我這個猴子!

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

⑶ 小結

1、鑒權需要實作doGetAuthorizationInfo方法

2、鑒權使用門面subject中方法進行鑒權

  以check開頭的會抛出異常

  以is和has開頭會傳回布爾值

四、Web項目內建Shiro

1.Web內建原理分析

⑴ web內建的配置

還記得嗎,以前我們在沒有與WEB環境進行內建的時候,為了生成SecurityManager對象,是通過手動讀取配置檔案生成工廠對象,再通過工廠對象擷取到SecurityManager的。就像下面代碼展示的那樣:

/**
 * @Description 登入方法
 */
private Subject shiroLogin(String loginName,String password) {
    //導入權限ini檔案建構權限工廠
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
    //工廠建構安全管理器
    SecurityManager securityManager = factory.getInstance();
    //使用SecurityUtils工具生效安全管理器
    SecurityUtils.setSecurityManager(securityManager);
    //使用SecurityUtils工具獲得主體
    Subject subject = SecurityUtils.getSubject();
    //建構賬号token
    UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginName, password);
    //登入操作
    subject.login(usernamePasswordToken);
    return subject;
}
           

不過,現在我們既然說要與WEB內建,那麼首先要做的事情就是把我們的shiro.ini這個配置檔案傳遞到WEB環境中,定義shiro.ini檔案如下:

#聲明自定義的realm,且為安全管理器指定realms
[main]
definitionRealm=com.kejizhentan.realm.DefinitionRealm
securityManager.realms=$definitionRealm
           

1) 建立項目

建立shiro-web-project項目

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

2) pom.xml配置

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.kejizhentan</groupId>
  <artifactId>shiro-web-project</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>shiro-web-project Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>

    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.3</version>
    </dependency>

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

    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.3.2</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <!-- tomcat7插件,指令: mvn tomcat7:run -DskipTests -->
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <uriEncoding>utf-8</uriEncoding>
          <port>8080</port>
          <path>/platform</path>
        </configuration>
      </plugin>

      <!-- compiler插件, 設定JDK版本 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>8</source>
          <target>8</target>
          <showWarnings>true</showWarnings>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
           

3) web.xml配置

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
  <display-name>shiro-web-project</display-name>

  <!-- 初始化SecurityManager對象所需要的環境-->
  <context-param>
    <param-name>shiroEnvironmentClass</param-name>
    <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
  </context-param>

  <!-- 指定Shiro的配置檔案的位置 -->
  <context-param>
    <param-name>shiroConfigLocations</param-name>
    <param-value>classpath:shiro.ini</param-value>
  </context-param>

  <!-- 監聽伺服器啟動時,建立shiro的web環境。
       即加載shiroEnvironmentClass變量指定的IniWebEnvironment類-->
  <listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
  </listener>

  <!-- shiro的l過濾入口,過濾一切請求 -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <!-- 過濾所有請求 -->
    <url-pattern>/*</url-pattern>
  </filter-mapping>

</web-app>
           

⑵ SecurityManager對象建立

上面我們內建shiro到web項目了,下面我們來追蹤下源碼,看下SecurityManager對象是如何建立的

(1)我啟動了伺服器,監聽器捕獲到了伺服器啟動事件。我現在所處的位置EnvironmentLoaderListener監聽器的入口處

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(2)進入方法内檢視,它先根據我們的shiroEnvironmentClass變量的值org.apache.shiro.web.env.IniWebEnvironment,初始化一個shiro環境對象

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

(3)最後在建立一個SecurityManager對象,再将其綁定到剛才通過位元組碼建立的Shiro環境對象中

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

到這來SecurityManager就完成了初始化

2. Shiro預設過濾器

Shiro内置了很多預設的過濾器,比如身份驗證、授權等相關的。預設過濾器可以參考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚舉過濾器

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

⑴ 認證相關

過濾器 過濾器類 說明 預設
authc FormAuthenticationFilter 基于表單的過濾器;如“/**=authc”,如果沒有登入會跳到相應的登入頁面登入
logout LogoutFilter 退出過濾器,主要屬性:redirectUrl:退出成功後重定向的位址,如“/logout=logout” /
anon AnonymousFilter 匿名過濾器,即不需要登入即可通路;一般用于靜态資源過濾;示例“/static/**=anon”

⑵ 授權相關

過濾器 過濾器類 說明 預設
roles RolesAuthorizationFilter 角色授權攔截器,驗證使用者是否擁有所有角色;主要屬性: loginUrl:登入頁面位址(/login.jsp);unauthorizedUrl:未授權後重定向的位址;示例“/admin/**=roles[admin]”
perms PermissionsAuthorizationFilter 權限授權攔截器,驗證使用者是否擁有所有權限;屬性和roles一樣;示例“/user/**=perms[“user:create”]”
port PortFilter 端口攔截器,主要屬性:port(80):可以通過的端口;示例“/test= port[80]”,如果使用者通路該頁面是非80,将自動将請求端口改為80并重定向到該80端口,其他路徑/參數等都一樣
rest HttpMethodPermissionFilter rest風格攔截器,自動根據請求方法建構權限字元串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)建構權限字元串;示例“/users=rest[user]”,會自動拼出“user:read,user:create,user:update,user:delete”權限字元串進行權限比對(所有都得比對,isPermittedAll)
ssl SslFilter SSL攔截器,隻有請求協定是https才能通過;否則自動跳轉會https端口(443);其他和port攔截器一樣;

3. Web內建完整案例

⑴ 項目結構如下

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

⑵ 添加pom.xml依賴

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.kejizhentan</groupId>
  <artifactId>shiro-web-project</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>shiro-web-project Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>


  <dependencies>

    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.3</version>
    </dependency>

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

    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.3.2</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <!-- tomcat7插件,指令: mvn tomcat7:run -DskipTests -->
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <uriEncoding>utf-8</uriEncoding>
          <port>8080</port>
          <path>/platform</path>
        </configuration>
      </plugin>

      <!-- compiler插件, 設定JDK版本 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>8</source>
          <target>8</target>
          <showWarnings>true</showWarnings>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
           

⑶ 編寫shiro.ini檔案

#聲明自定義的realm,且為安全管理器指定realms
[main]
definitionRealm=com.kejizhentan.realm.DefinitionRealm
securityManager.realms=$definitionRealm

#使用者退出後跳轉指定JSP頁面
logout.redirectUrl=/login.jsp
#若沒有登入,則被authc過濾器重定向到login.jsp頁面
authc.loginUrl = /login.jsp
[urls]
/login=anon
#發送/home請求需要先登入
/home= authc
#發送/order/list請求需要先登入
/order-list = roles[admin]
#送出代碼需要order:add權限
/order-add = perms["order:add"]
#更新代碼需要order:del權限
/order-del = perms["order:del"]
#發送退出請求則用退出過濾器
/logout = logout
           

⑷ 相關工具類

封裝base64和16進制編碼解碼工具類EncodesUtil.java

package com.kejizhentan.utils;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.Hex;

/**
 * @Description:封裝base64和16進制編碼解碼工具類
 */
public class EncodesUtil {

    /**
     * @Description HEX-byte[]--String轉換
     * @param input 輸入數組
     * @return String
     */
    public static String encodeHex(byte[] input){
        return Hex.encodeToString(input);
    }

    /**
     * @Description HEX-String--byte[]轉換
     * @param input 輸入字元串
     * @return byte數組
     */
    public static byte[] decodeHex(String input){
        return Hex.decode(input);
    }

    /**
     * @Description Base64-byte[]--String轉換
     * @param input 輸入數組
     * @return String
     */
    public static String encodeBase64(byte[] input){
        return Base64.encodeToString(input);
    }

    /**
     * @Description Base64-String--byte[]轉換
     * @param input 輸入字元串
     * @return byte數組
     */
    public static byte[] decodeBase64(String input){
        return Base64.decode(input);
    }

}
           

雜湊演算法加密工具類HashUtil.java

package com.kejizhentan.utils;

import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;

import java.util.HashMap;
import java.util.Map;

/**
 * @Auther: kejizhentan
 * @Date 2022/12/31 14:20
 * @Description: 雜湊演算法加密工具類
 */
public class HashUtil {
    public static final String SHA1 = "SHA-1";

    public static final Integer ITERATIONS =512;

    /**
     * @Description sha1方法
     * @param input 需要散列字元串
     * @param salt 鹽字元串
     * @return
     */
    public static String sha1(String input, String salt) {
        return new SimpleHash(SHA1, input, salt,ITERATIONS).toString();
    }

    /**
     * @Description 随機獲得salt字元串
     * @return
     */
    public static String generateSalt(){
        SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
        return randomNumberGenerator.nextBytes().toHex();
    }


    /**
     * @Description 生成密碼字元密文和salt密文
     * @param
     * @return
     */
    public static Map<String,String> entryptPassword(String passwordPlain) {
        Map<String,String> map = new HashMap<>();
        //随機獲得salt字元串
        String salt = generateSalt();
        //通過加鹽的方式生成加密後的密碼
        String password =sha1(passwordPlain,salt);
        map.put("salt", salt);
        map.put("password", password);
        return map;
    }
}
           

⑸ 建立DefinitionRealm.java

package com.kejizhentan.realm;

import com.kejizhentan.service.SecurityService;
import com.kejizhentan.service.impl.SecurityServiceImpl;
import com.kejizhentan.utils.HashUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import java.util.List;
import java.util.Map;

public class DefinitionRealm extends AuthorizingRealm {
    /**
     * @Description 構造函數
     */
    public DefinitionRealm() {
        //指定密碼比對方式為sha1
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(HashUtil.SHA1);
        //指定密碼疊代次數
        matcher.setHashIterations(HashUtil.ITERATIONS);
        //使用父親方法使比對方式生效
        setCredentialsMatcher(matcher);
    }
    /**
     * @Description 認證接口
     * @param token 傳遞登入token
     * @return
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //從AuthenticationToken中獲得登入名稱
        String loginName = (String) token.getPrincipal();
        SecurityService securityService = new SecurityServiceImpl();
        Map<String, String> map = securityService.findPasswordByLoginName(loginName);
        if (map.isEmpty()){
            throw new UnknownAccountException("賬戶不存在");
        }
        String salt = map.get("salt");
        String password = map.get("password");
        //傳遞賬号和密碼:參數1:緩存對象,參數2:密文密碼,參數三:位元組salt,參數4:目前DefinitionRealm名稱
        return  new SimpleAuthenticationInfo(loginName,password, ByteSource.Util.bytes(salt),getName());
    }


    /**
     * @Description 授權方法
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //拿到使用者認證憑證資訊
        String loginName = (String) principals.getPrimaryPrincipal();
        //從資料庫中查詢對應的角色和資源
        SecurityService securityService = new SecurityServiceImpl();
        List<String> roles = securityService.findRoleByloginName(loginName);
        List<String> permissions = securityService.findPermissionByloginName(loginName);
        //建構資源校驗
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRoles(roles);
        authorizationInfo.addStringPermissions(permissions);
        return authorizationInfo;
    }
}
           

⑹ 編寫LoginService

package com.kejizhentan.service;

import org.apache.shiro.authc.UsernamePasswordToken;

/**
 * @Description:登入服務
 */
public interface LoginService {

    /**
     * @Description 登入方法
     * @param token 登入對象
     * @return
     */
    boolean login(UsernamePasswordToken token);

    /**
     * @Description 登出方法
     */
    void logout();
}
           
package com.kejizhentan.service.impl;

import com.kejizhentan.service.LoginService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;


/**
 * @Description:登入服務
 */
public class LoginServiceImpl implements LoginService {

    @Override
    public boolean login(UsernamePasswordToken token) {
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
        }catch (Exception e){
            return false;
        }
        return subject.isAuthenticated();
    }

    @Override
    public void logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
    }
}
           

⑺ 編寫SecurityService

package com.kejizhentan.service;

import java.util.List;
import java.util.Map;

/**
 * @Description:權限服務接口
 */
public interface SecurityService {

    /**
     * @Description 查找角色按使用者登入名
     * @param  loginName 登入名稱
     * @return
     */
    List<String> findRoleByloginName(String loginName);

    /**
     * @Description 查找資源按使用者登入名
     * @param  loginName 登入名稱
     * @return
     */
    List<String>  findPermissionByloginName(String loginName);

    /**
     * @Description 查找密碼按使用者登入名
     * @param loginName 登入名稱
     * @return
     */
    Map<String,String> findPasswordByLoginName(String loginName);
}
           
package com.kejizhentan.service.impl;

import com.kejizhentan.service.SecurityService;
import com.kejizhentan.utils.HashUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Description:權限服務層
 */
public class SecurityServiceImpl implements SecurityService {

    @Override
    public Map<String,String> findPasswordByLoginName(String loginName) {
        return HashUtil.entryptPassword("123456");
    }

    @Override
    public List<String> findRoleByloginName(String loginName) {
        List<String> list = new ArrayList<>();
        if ("admin".equals(loginName)){
            list.add("admin");
        }
        list.add("dev");
        return list;
    }

    @Override
    public List<String>  findPermissionByloginName(String loginName) {
        List<String> list = new ArrayList<>();
        if ("zhangsan".equals(loginName)){
            list.add("order:list");
            list.add("order:add");
            list.add("order:del");
        }
        return list;
    }
}
           

⑻ 添加web層内容

1) LoginServlet

package com.kejizhentan.servlet;

import com.kejizhentan.service.LoginService;
import com.kejizhentan.service.impl.LoginServiceImpl;
import org.apache.shiro.authc.UsernamePasswordToken;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description:登入方法
 */
@WebServlet(urlPatterns = "/login")
public class LoginServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        //擷取輸入的帳号密碼
        String username = req.getParameter("loginName");
        String password = req.getParameter("password");
        //封裝使用者資料,成為Shiro能認識的token辨別
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        LoginService loginService = new LoginServiceImpl();
        //将封裝使用者資訊的token進行驗證
        boolean isLoginSuccess = loginService.login(token);
        if (!isLoginSuccess) {
            //重定向到未登入成功頁面
            resp.sendRedirect("login.jsp");
            return;
        }
        req.getRequestDispatcher("/home").forward(req, resp);
    }

}
           

2)HomeServlet

package com.kejizhentan.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description:系統home頁面
 */
@WebServlet(urlPatterns = "/home")
public class HomeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        req.getRequestDispatcher("home.jsp").forward(req, resp);
    }
}
           

3)OrderAddServlet

package com.kejizhentan.servlet;


import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description:添加頁碼
 */
@WebServlet(urlPatterns = "/order-add")
public class OrderAddServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        req.getRequestDispatcher("order-add.jsp").forward(req, resp);
    }

}
           

4)OrderListServlet

package com.kejizhentan.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description:訂單清單
 */
@WebServlet(urlPatterns = "/order-list")
public class OrderListServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        req.getRequestDispatcher("order-list.jsp").forward(req, resp);
    }
}
           

5)LogoutServlet

package com.kejizhentan.servlet;


import com.kejizhentan.service.LoginService;
import com.kejizhentan.service.impl.LoginServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description:登出
 */
@WebServlet(urlPatterns = "/logout")
public class LogoutServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        LoginService loginService = new LoginServiceImpl();
        loginService.logout();
    }

}
           

6)web.xml

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
  <display-name>shiro-web-project</display-name>

  <!-- 初始化SecurityManager對象所需要的環境-->
  <context-param>
    <param-name>shiroEnvironmentClass</param-name>
    <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
  </context-param>

  <!-- 指定Shiro的配置檔案的位置 -->
  <context-param>
    <param-name>shiroConfigLocations</param-name>
    <param-value>classpath:shiro.ini</param-value>
  </context-param>

  <!-- 監聽伺服器啟動時,建立shiro的web環境。
       即加載shiroEnvironmentClass變量指定的IniWebEnvironment類-->
  <listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
  </listener>

  <!-- shiro的l過濾入口,過濾一切請求 -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <!-- 過濾所有請求 -->
    <url-pattern>/*</url-pattern>
  </filter-mapping>

</web-app>
           

⑼ 添加JSP

login.jsp登入頁面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post" action="${pageContext.request.contextPath}/login">
    <table>
        <tr>
            <th>登陸名稱</th>
            <td><input type="text"  name="loginName"></td>
        </tr>
        <tr>
            <th>密碼</th>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="送出"/>
            </td>
        </tr>
    </table>

</form>
</body>
</html>
           

home.jsp系統頁

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title></title>
</head>
<body>
<h6>
    <a href="${pageContext.request.contextPath}/logout">退出</a>
    <a href="${pageContext.request.contextPath}/order-list">清單</a>
    <a href="${pageContext.request.contextPath}/order-add">添加</a>
</h6>
</body>
</html>
           

order-add.jsp訂單添加(僞代碼)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Title</title>
</head>
<body>
添加頁面
</body>
</html>
           

order-list.jsp訂單清單

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--導入jstl标簽庫--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>使用者清單jsp頁面</title>
    <style>
        table {border:1px solid #000000}
        table th{border:1px solid #000000}
        table td{border:1px solid #000000}
    </style>

</head>
<body>
<table cellpadding="0" cellspacing="0" width="80%">
    <tr>
        <th>編号</th>
        <th>公司名稱</th>
        <th>資訊來源</th>
        <th>所屬行業</th>
        <th>級别</th>
        <th>聯系位址</th>
        <th>聯系電話</th>
    </tr>
    <tr>
        <td>1</td>
        <td>柯基偵探</td>
        <td>網絡營銷</td>
        <td>網際網路</td>
        <td>普通客戶</td>
        <td>北京市東城區</td>
        <td>8888888888</td>
    </tr>
    <tr>
        <td>2</td>
        <td>柯基偵探微信公衆号</td>
        <td>j2ee</td>
        <td>網際網路</td>
        <td>VIP客戶</td>
        <td>北京市東城區</td>
        <td>8888888887</td>
    </tr>
    <tr>
        <td>3</td>
        <td>柯基</td>
        <td>大資料</td>
        <td>網際網路</td>
        <td>VIP客戶</td>
        <td>北京市東城區</td>
        <td>888888888888</td>
    </tr>
</table>
</body>

</html>
           

⑽ 測試

1) 啟動操作

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

點選apply然後點選OK

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

2) 登入過濾

通路http://localhost:8080/platform/home的時候,會被攔截,跳轉到登入頁面

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

3) 角色過濾

使用

admin

使用者登入,密碼:

123456

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

根據SecurityServiceImpl我們可以知道使用admin賬号

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

登入成功之後:

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

此時點選“清單”,因為目前admin使用者是有admin角色

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

是以能正常通路“清單”中的内容

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

點選“添加”,因為目前admin使用者是沒有order:add的資源

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

是以回401

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

4) 資源過濾

點選“退出”,使用“zhangsan”使用者登入,密碼為123456 ,點選“添加”

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

因為SecurityServiceImpl中為zhangsan使用者添加如下的資源

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

點選“添加”之後正常通路

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

點選“清單”之後,因為“zhangsan”使用者沒有“admin”角色,是以通路受限

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

4. web項目授權

前面我們學習了基于

ini

檔案配置方式來完成授權,下面我們來看下其他2種方式的授權

⑴ 基于代碼

1) 登入相關

Subject 登入相關方法 描述

isAuthenticated()

傳回true 表示已經登入,否則傳回false。

2)角色相關

Subject 角色相關方法 描述

hasRole(String roleName)

如果Subject 被配置設定了指定的角色,傳回true ,否則傳回false。

hasRoles(List<String> roleNames)

如果Subject 被配置設定了所有指定的角色,傳回true ,否則傳回false。

hasAllRoles(Collection<String>roleNames)

傳回一個與方法參數中目錄一緻的hasRole 結果的集合。有性能的提高如果許多角色需要執行檢查(例如,當自定義一個複雜的視圖)。

checkRole(String roleName)

沒有傳回值,如果Subject 被配置設定了指定的角色,不然的話就抛出AuthorizationException。

checkRoles(Collection<String>roleNames)

沒有傳回值,如果Subject 被配置設定了所有的指定的角色,不然的話就抛出AuthorizationException。

checkRoles(String… roleNames)

與上面的checkRoles 方法的效果相同,但允許Java5 的var-args 類型的參數

3) 資源相關

Subject 資源相關方法 描述

isPermitted(Permission p)

如果該Subject 被允許執行某動作或通路被權限執行個體指定的資源,傳回true,否則傳回false

isPermitted(List<Permission> perms)

傳回一個與方法參數中目錄一緻的isPermitted 結果的集合。

isPermittedAll(Collection<Permission>perms)

如果該Subject 被允許所有指定的權限,傳回true,否則傳回false有性能的提高如果需要執行許多檢查(例如,當自定義一個複雜的視圖)

isPermitted(String perm)

如果該Subject 被允許執行某動作或通路被字元串權限指定的資源,傳回true,否則傳回false。

isPermitted(String…perms)

傳回一個與方法參數中目錄一緻的isPermitted 結果的數組。有性能的提高如果許多字元串權限檢查需要被執行(例如,當自定義一個複雜的視圖)。

isPermittedAll(String…perms)

如果該Subject 被允許所有指定的字元串權限,傳回true,否則傳回false。

checkPermission(Permission p)

沒有傳回值,如果Subject 被允許執行某動作或通路被特定的權限執行個體指定的資源,不然的話就抛出AuthorizationException 異常。

checkPermission(String perm)

沒有傳回值,如果Subject 被允許執行某動作或通路被特定的字元串權限指定的資源,不然的話就抛出AuthorizationException 異常。

checkPermissions(Collection<Permission> perms)

沒有傳回值,如果Subject 被允許所有的權限,不然的話就抛出AuthorizationException 異常。有性能的提高如果需要執行許多檢查(例如,當自定義一個複雜的視圖)

checkPermissions(String… perms)

和上面的checkPermissions 方法效果相同,但是使用的是基于字元串的權限。

4) 案例如下

① 項目結構如下
二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro
② 添加pom.xml依賴
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.kejizhentan</groupId>
  <artifactId>shiro-web-project</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>shiro-web-project Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>


  <dependencies>

    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.3</version>
    </dependency>

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

    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.3.2</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <!-- tomcat7插件,指令: mvn tomcat7:run -DskipTests -->
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <uriEncoding>utf-8</uriEncoding>
          <port>8080</port>
          <path>/platform</path>
        </configuration>
      </plugin>

      <!-- compiler插件, 設定JDK版本 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>8</source>
          <target>8</target>
          <showWarnings>true</showWarnings>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
           
③ 編寫shiro.ini檔案
#聲明自定義的realm,且為安全管理器指定realms
[main]
definitionRealm=com.kejizhentan.realm.DefinitionRealm
securityManager.realms=$definitionRealm

#使用者退出後跳轉指定JSP頁面
logout.redirectUrl=/login.jsp
#若沒有登入,則被authc過濾器重定向到login.jsp頁面
authc.loginUrl = /login.jsp
[urls]
/login=anon
#發送退出請求則用退出過濾器
/logout = logout
           
④ 相關工具類

封裝base64和16進制編碼解碼工具類EncodesUtil.java

package com.kejizhentan.utils;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.Hex;

/**
 * @Description:封裝base64和16進制編碼解碼工具類
 */
public class EncodesUtil {

    /**
     * @Description HEX-byte[]--String轉換
     * @param input 輸入數組
     * @return String
     */
    public static String encodeHex(byte[] input){
        return Hex.encodeToString(input);
    }

    /**
     * @Description HEX-String--byte[]轉換
     * @param input 輸入字元串
     * @return byte數組
     */
    public static byte[] decodeHex(String input){
        return Hex.decode(input);
    }

    /**
     * @Description Base64-byte[]--String轉換
     * @param input 輸入數組
     * @return String
     */
    public static String encodeBase64(byte[] input){
        return Base64.encodeToString(input);
    }

    /**
     * @Description Base64-String--byte[]轉換
     * @param input 輸入字元串
     * @return byte數組
     */
    public static byte[] decodeBase64(String input){
        return Base64.decode(input);
    }

}
           

雜湊演算法加密工具類HashUtil.java

package com.kejizhentan.utils;

import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;

import java.util.HashMap;
import java.util.Map;

/**
 * @Auther: kejizhentan
 * @Date 2022/12/31 14:20
 * @Description: 雜湊演算法加密工具類
 */
public class HashUtil {
    public static final String SHA1 = "SHA-1";

    public static final Integer ITERATIONS =512;

    /**
     * @Description sha1方法
     * @param input 需要散列字元串
     * @param salt 鹽字元串
     * @return
     */
    public static String sha1(String input, String salt) {
        return new SimpleHash(SHA1, input, salt,ITERATIONS).toString();
    }

    /**
     * @Description 随機獲得salt字元串
     * @return
     */
    public static String generateSalt(){
        SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
        return randomNumberGenerator.nextBytes().toHex();
    }


    /**
     * @Description 生成密碼字元密文和salt密文
     * @param
     * @return
     */
    public static Map<String,String> entryptPassword(String passwordPlain) {
        Map<String,String> map = new HashMap<>();
        //随機獲得salt字元串
        String salt = generateSalt();
        //通過加鹽的方式生成加密後的密碼
        String password =sha1(passwordPlain,salt);
        map.put("salt", salt);
        map.put("password", password);
        return map;
    }
}
           
⑤ 建立DefinitionRealm.java
package com.kejizhentan.realm;

import com.kejizhentan.service.SecurityService;
import com.kejizhentan.service.impl.SecurityServiceImpl;
import com.kejizhentan.utils.HashUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import java.util.List;
import java.util.Map;

public class DefinitionRealm extends AuthorizingRealm {
    /**
     * @Description 構造函數
     */
    public DefinitionRealm() {
        //指定密碼比對方式為sha1
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(HashUtil.SHA1);
        //指定密碼疊代次數
        matcher.setHashIterations(HashUtil.ITERATIONS);
        //使用父親方法使比對方式生效
        setCredentialsMatcher(matcher);
    }
    /**
     * @Description 認證接口
     * @param token 傳遞登入token
     * @return
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //從AuthenticationToken中獲得登入名稱
        String loginName = (String) token.getPrincipal();
        SecurityService securityService = new SecurityServiceImpl();
        Map<String, String> map = securityService.findPasswordByLoginName(loginName);
        if (map.isEmpty()){
            throw new UnknownAccountException("賬戶不存在");
        }
        String salt = map.get("salt");
        String password = map.get("password");
        //傳遞賬号和密碼:參數1:緩存對象,參數2:密文密碼,參數三:位元組salt,參數4:目前DefinitionRealm名稱
        return  new SimpleAuthenticationInfo(loginName,password, ByteSource.Util.bytes(salt),getName());
    }


    /**
     * @Description 授權方法
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //拿到使用者認證憑證資訊
        String loginName = (String) principals.getPrimaryPrincipal();
        //從資料庫中查詢對應的角色和資源
        SecurityService securityService = new SecurityServiceImpl();
        List<String> roles = securityService.findRoleByloginName(loginName);
        List<String> permissions = securityService.findPermissionByloginName(loginName);
        //建構資源校驗
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRoles(roles);
        authorizationInfo.addStringPermissions(permissions);
        return authorizationInfo;
    }
}
           
⑥ 編寫LoginService
package com.kejizhentan.service;

import org.apache.shiro.authc.UsernamePasswordToken;

/**
 * @Description:登入服務
 */
public interface LoginService {

    /**
     * @Description 登入方法
     * @param token 登入對象
     * @return
     */
    boolean login(UsernamePasswordToken token);

    /**
     * @Description 登出方法
     */
    void logout();
}
           
package com.kejizhentan.service.impl;

import com.kejizhentan.service.LoginService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;


/**
 * @Description:登入服務
 */
public class LoginServiceImpl implements LoginService {

    @Override
    public boolean login(UsernamePasswordToken token) {
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
        }catch (Exception e){
            return false;
        }
        return subject.isAuthenticated();
    }

    @Override
    public void logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
    }
}
           
⑦ 編寫SecurityService
package com.kejizhentan.service;

import java.util.List;
import java.util.Map;

/**
 * @Description:權限服務接口
 */
public interface SecurityService {

    /**
     * @Description 查找角色按使用者登入名
     * @param  loginName 登入名稱
     * @return
     */
    List<String> findRoleByloginName(String loginName);

    /**
     * @Description 查找資源按使用者登入名
     * @param  loginName 登入名稱
     * @return
     */
    List<String>  findPermissionByloginName(String loginName);

    /**
     * @Description 查找密碼按使用者登入名
     * @param loginName 登入名稱
     * @return
     */
    Map<String,String> findPasswordByLoginName(String loginName);
}
           
package com.kejizhentan.service.impl;

import com.kejizhentan.service.SecurityService;
import com.kejizhentan.utils.HashUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Description:權限服務層
 */
public class SecurityServiceImpl implements SecurityService {

    @Override
    public Map<String,String> findPasswordByLoginName(String loginName) {
        return HashUtil.entryptPassword("123456");
    }

    @Override
    public List<String> findRoleByloginName(String loginName) {
        List<String> list = new ArrayList<>();
        if ("admin".equals(loginName)){
            list.add("admin");
        }
        list.add("dev");
        return list;
    }

    @Override
    public List<String>  findPermissionByloginName(String loginName) {
        List<String> list = new ArrayList<>();
        if ("zhangsan".equals(loginName)){
            list.add("order:list");
            list.add("order:add");
            list.add("order:del");
        }
        return list;
    }
}
           
⑧ 添加web層内容

○ LoginServlet

package com.kejizhentan.servlet;

import com.kejizhentan.service.LoginService;
import com.kejizhentan.service.impl.LoginServiceImpl;
import org.apache.shiro.authc.UsernamePasswordToken;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description:登入方法
 */
@WebServlet(urlPatterns = "/login")
public class LoginServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        //擷取輸入的帳号密碼
        String username = req.getParameter("loginName");
        String password = req.getParameter("password");
        //封裝使用者資料,成為Shiro能認識的token辨別
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        LoginService loginService = new LoginServiceImpl();
        //将封裝使用者資訊的token進行驗證
        boolean isLoginSuccess = loginService.login(token);
        if (!isLoginSuccess) {
            //重定向到未登入成功頁面
            resp.sendRedirect("login.jsp");
            return;
        }
        req.getRequestDispatcher("/home").forward(req, resp);
    }

}
           

○ 登入相關 HomeServlet

package com.kejizhentan.servlet;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description:系統home頁面
 */
@WebServlet(urlPatterns = "/home")
public class HomeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        //通過subjectd對象去判斷是否登入
        Subject subject = SecurityUtils.getSubject();
        boolean flag  = subject.isAuthenticated();
        if (flag){
            resp.sendRedirect("home.jsp");
        }else {
            req.getRequestDispatcher("/login").forward(req, resp);
        }
    }
}
           

通路http://localhost:8080/platform/home 進行debug

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

此時我們通過subject.isAuthenticated()判斷是否登入,如果登入則重定向到home.jsp,如果沒有登入則轉發到/login對應的servlet

○ 資源相關OrderAddServlet

package com.kejizhentan.servlet;


import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description:添加頁碼
 */
@WebServlet(urlPatterns = "/order-add")
public class OrderAddServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        Subject subject = SecurityUtils.getSubject();
        //判斷是否有對應資源
        boolean flag = subject.isPermitted("order:add");
        if (flag){
            req.getRequestDispatcher("order-add.jsp").forward(req, resp);
        }else {
            req.getRequestDispatcher("/login").forward(req, resp);
        }
    }

}
           

通路http://localhost:8080/platform/order-add

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

因為此時我未登入,也就是說目前沒有order:add資源,通過 subject.isPermitted(“order:add”)傳回false

○ 角色相關 OrderListServlet

package com.kejizhentan.servlet;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description:訂單清單
 */
@WebServlet(urlPatterns = "/order-list")
public class OrderListServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        Subject subject = SecurityUtils.getSubject();
        //判斷目前角色
        boolean flag = subject.hasRole("admin");
        if (flag){
            req.getRequestDispatcher("order-list.jsp").forward(req, resp);
        }else {
            req.getRequestDispatcher("/login").forward(req, resp);
        }
    }
}
           

通路 http://localhost:8080/platform/order-list

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

因為此時我未登入,也就是說目前沒有admin角色,這是通過subject.hasRole(“admin”)傳回未false

○ LogoutServlet

package com.kejizhentan.servlet;


import com.kejizhentan.service.LoginService;
import com.kejizhentan.service.impl.LoginServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description:登出
 */
@WebServlet(urlPatterns = "/logout")
public class LogoutServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        LoginService loginService = new LoginServiceImpl();
        loginService.logout();
    }

}
           

○ web.xml

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
  <display-name>shiro-web-project</display-name>

  <!-- 初始化SecurityManager對象所需要的環境-->
  <context-param>
    <param-name>shiroEnvironmentClass</param-name>
    <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
  </context-param>

  <!-- 指定Shiro的配置檔案的位置 -->
  <context-param>
    <param-name>shiroConfigLocations</param-name>
    <param-value>classpath:shiro.ini</param-value>
  </context-param>

  <!-- 監聽伺服器啟動時,建立shiro的web環境。
       即加載shiroEnvironmentClass變量指定的IniWebEnvironment類-->
  <listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
  </listener>

  <!-- shiro的l過濾入口,過濾一切請求 -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <!-- 過濾所有請求 -->
    <url-pattern>/*</url-pattern>
  </filter-mapping>

</web-app>
           

○ 添加JSP

login.jsp登入頁面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post" action="${pageContext.request.contextPath}/login">
    <table>
        <tr>
            <th>登陸名稱</th>
            <td><input type="text"  name="loginName"></td>
        </tr>
        <tr>
            <th>密碼</th>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="送出"/>
            </td>
        </tr>
    </table>

</form>
</body>
</html>
           

home.jsp系統頁

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title></title>
</head>
<body>
<h6>
    <a href="${pageContext.request.contextPath}/logout">退出</a>
    <a href="${pageContext.request.contextPath}/order-list">清單</a>
    <a href="${pageContext.request.contextPath}/order-add">添加</a>
</h6>
</body>
</html>
           

order-add.jsp訂單添加(僞代碼)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Title</title>
</head>
<body>
添加頁面
</body>
</html>
           

order-list.jsp訂單清單

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--導入jstl标簽庫--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>使用者清單jsp頁面</title>
    <style>
        table {border:1px solid #000000}
        table th{border:1px solid #000000}
        table td{border:1px solid #000000}
    </style>

</head>
<body>
<table cellpadding="0" cellspacing="0" width="80%">
    <tr>
        <th>編号</th>
        <th>公司名稱</th>
        <th>資訊來源</th>
        <th>所屬行業</th>
        <th>級别</th>
        <th>聯系位址</th>
        <th>聯系電話</th>
    </tr>
    <tr>
        <td>1</td>
        <td>柯基偵探</td>
        <td>網絡營銷</td>
        <td>網際網路</td>
        <td>普通客戶</td>
        <td>北京市東城區</td>
        <td>8888888888</td>
    </tr>
    <tr>
        <td>2</td>
        <td>柯基偵探微信公衆号</td>
        <td>j2ee</td>
        <td>網際網路</td>
        <td>VIP客戶</td>
        <td>北京市東城區</td>
        <td>8888888887</td>
    </tr>
    <tr>
        <td>3</td>
        <td>柯基</td>
        <td>大資料</td>
        <td>網際網路</td>
        <td>VIP客戶</td>
        <td>北京市東城區</td>
        <td>888888888888</td>
    </tr>
</table>
</body>

</html>
           

⑵ 基于Jsp标簽

1) 使用方式

Shiro提供了一套JSP标簽庫來實作頁面級的授權控制, 在使用Shiro标簽庫前,首先需要在JSP引入shiro标簽:

2) 相關标簽

标簽 說明

< shiro:guest >

驗證目前使用者是否為“訪客”,即未認證(包含未記住)的使用者

< shiro:user >

認證通過或已記住的使用者

< shiro:authenticated >

已認證通過的使用者。不包含已記住的使用者,這是與user标簽的差別所在

< shiro:notAuthenticated >

未認證通過使用者。與guest标簽的差別是,該标簽包含已記住使用者

< shiro:principal />

輸出目前使用者資訊,通常為登入帳号資訊

< shiro:hasRole name="角色">

驗證目前使用者是否屬于該角色

< shiro:lacksRole name="角色">

與hasRole标簽邏輯相反,當使用者不屬于該角色時驗證通過

< shiro:hasAnyRoles name="a,b">

驗證目前使用者是否屬于以下任意一個角色

<shiro:hasPermission name=“資源”>

驗證目前使用者是否擁有制定權限

<shiro:lacksPermission name="資源">

與permission标簽邏輯相反,目前使用者沒有制定權限時,驗證通過

3) 案例

① 修改home.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title></title>
</head>
<body>
<h6>
    <a href="${pageContext.request.contextPath}/logout">退出</a>
    <shiro:hasRole name="admin">
    <a href="${pageContext.request.contextPath}/order-list">清單</a>
    </shiro:hasRole>
    <shiro:hasPermission name="order:add">
    <a href="${pageContext.request.contextPath}/order-add">添加</a>
    </shiro:hasPermission>
</h6>
</body>
</html>
           
② 測試

通路http://localhost:8080/platform/login

使用admin/123456登入

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

這個時候我們隻能看見“清單”,看不見“添加”,點選“退出”

使用zhangsan/123456登入

二十三、shiro安全架構詳解(一)一、 權限概述二、 Shiro概述三、 Shiro入門四、Web項目內建Shiro

這個時候我們隻能看見“添加”,看不見“清單”,點選“退出”

需要注意的是,這裡隻是頁面是否顯示内容,不能防止盜鍊的發生