Shiro入門學習

image

學習一門新技術的思路html

是什麼?vue

是用來幹嗎的,應用在哪些地方?java

爲何要學?mysql

與它相關的有哪些,須要另外瞭解的知識?jquery

怎樣去學?ios

文章主要內容:

  • 1、什麼是Shiro——是什麼?
  • 2、Shiro的功能及具體特性(優點)——是用來幹嗎的應用在哪些地方?爲何要學?
  • 3、【拓展】RBAC(以角色爲基礎的訪問控制)——須要另外瞭解的理論基礎
  • 4、與其相關的SpringSecurity框架——與它相關的有哪些?
  • 5、第一個簡單純粹的Shiro程序——怎麼學?先會用跑起來再逐步理解。幹!
  • 6、Shiro的架構(三大核心組件)
  • 7、Shiro集成SpringBoot和數據庫使用
  • 8、使用Shiro的坑
  • 9、總結

1、什麼是Shiro

官方文檔:git

http://shiro.apache.org/index.htmlgithub

官方介紹:web

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

  • Apache Shiro 是一個簡單易用的Java 安全(權限)框架
  • Shiro 能夠很是容易的開發出足夠好的應用,其不只能夠用在JavaSE環境,也能夠用在JavaEE環 境。(便可以不須要Web和EJB等容器支持)
  • 能夠提供認證、受權、加密、會話管理,Web集成,緩存等。

2、Shiro的功能特性(優點)

image

Primary Concerns:

  • Authentication(認證):用戶身份識別,即一個用戶是怎樣的角色,是管理員或者普通用戶?一般被稱爲用戶「登陸」
  • Authorization(受權):訪問控制。好比某個用戶是否具備某個刪除操做的使用權限。
  • Session Management(會話管理):特定於用戶的會話管理,即便在非web 或 EJB 應用程序,可任意使用Session API。能夠響應認證、訪問控制,或者Session生命週期中發生的事件。
  • Cryptography(加密):在對數據源使用加密算法加密的同時,保證易於使用。

Supporting Features:

  • Web Support(Web支持):Shiro的Web支持API有助於保護Web應用程序。能夠很是容易的集成到Web環境。
  • Caching(緩存):緩存是Apache Shiro API中的第一級,以確保安全操做保持快速和高效。好比用戶登陸後,其用戶信息,擁有的角色、權限沒必要每次去查,這樣能夠提升效率
  • Concurrency(併發性):Apache Shiro支持多線程應用的併發驗證。即,如在一個線程中開啓另外一個線程,能把權限自動的傳播過去。
  • Testing(測試):存在測試支持,可幫助編寫單元測試和集成測試,並確保代碼按預期獲得保障。
  • Run As(運行方式):容許用戶承擔(僞裝)另外一個用戶的身份(若是容許)的功能,有時在管理方案中頗有用。
  • Remember Me:支持提供「Remember Me」服務,獲取用戶關聯信息而無需登陸
  • 可將一個或以上用戶安全數據源數據組合成一個複合的用戶 「view」(視圖)
  • 支持單點登陸(SSO)功能

......

但,Shiro不會去維護用戶、維護權限,這些須要咱們本身去設計/提供,而後經過相應的接口注入給Shiro

其中Authentication(認證), Authorization(受權), Session Management(會話管理), Cryptography(加密)被 Shiro 框架的開發團隊稱之爲應用安全的四大基石

優勢(爲何要學?):

易於使用、全面、靈活、Web支持、低耦合、被普遍支持普遍使用(是Apache軟件基金會的一部分)

公司要用、面試要問。。。

3、【拓展】RBAC(以角色爲基礎的訪問控制)

一、什麼是RBAC

維基百科:以角色爲基礎的訪問控制Role-based access controlRBAC),是資訊安全領域中,一種較新且廣爲使用的訪問控制機制,不直接賦予使用者權限,而是將權限賦予角色。

RBAC經過角色關聯用戶,角色關聯權限的方式間接賦予用戶權限。以下圖
image

有人會問爲何不直接給用戶分配權限,還畫蛇添足的增長角色這一環節呢?

實際上是能夠直接給用戶分配權限,只是直接給用戶分配權限,少了一層關係,擴展性弱了許多,適合那些用戶數量、角色類型少的平臺。

對於一般的系統,好比:存在多個用戶擁有相同的權限,在分配的時候就要分別爲這幾個用戶指定相同的權限,修改時也要爲這幾個用戶的權限進行一一修改。有了角色後,咱們只須要爲該角色制定好權限後,將相同權限的用戶都指定爲同一個角色便可,便於權限管理。

對於批量的用戶權限調整,只需調整用戶關聯的角色權限,無需對每個用戶都進行權限調整,既大幅提高權限調整的效率,又下降了漏調權限的機率。

小結:

RBAC 的優勢主要在於易用和高效。給用戶受權時只須要對角色受權,而後將相應的角色分配給用戶便可;從技術角度講,思路清晰且易於實現,且後期維護時只須要維護關係模型,顯得簡單而高效。

RBAC 的缺點主要有兩個:一個是在進行較爲複雜的權限校驗時須要不斷地遍歷和遞歸,會形成必定的性能影響。另外一個是缺乏數據權限模型,基於 RBAC 來實現數據權限校驗比較複雜和低效。

如今主流的權限管理系統設計大多仍是基於RBAC模型的,只是根據不一樣的業務和設計方案,呈現不一樣的顯示效果。

二、RBAC模型的分類

RBAC模型能夠分爲:RBAC0、RBAC一、RBAC二、RBAC3 四種。其中RBAC0是基礎,也是最簡單的,至關於底層邏輯,RBAC一、RBAC二、RBAC3都是以RBAC0爲基礎的升級。

通常狀況下,使用RBAC0模型就能夠知足常規的權限管理系統設計了。

RBAC0模型:

最簡單的用戶、角色、權限模型。是基礎,定義了能構成 RBAC 權限控制系統的最小的集合。

RBAC0 由四部分構成:

  • 用戶(User) 權限的使用主體
  • 角色(Role) 包含許可的集合
  • 會話(Session)綁定用戶和角色關係映射的中間通道。並且用戶必須經過會話才能給用戶設置角色。
  • 許可(Pemission) 對特定資源的特定的訪問許可。

image

RBAC0對應的表結構:

RBAC0 裏面又包含了2種(用戶和角色的表關係):

  1. 用戶和角色是多對一關係,即:一個用戶只充當一種角色,一種角色能夠有多個用戶擔當。
  2. 用戶和角色是多對多關係,即:一個用戶可同時充當多種角色,一種角色能夠有多個用戶擔當。

那麼,何時該使用多對一的權限體系,何時又該使用多對多的權限體系呢?

若是系統功能比較單一,使用人員較少,崗位權限相對清晰且確保不會出現兼崗的狀況,此時能夠考慮用多對一的權限體系。其他狀況儘可能使用多對多的權限體系,保證系統的可擴展性。如:張三既是行政,也負責財務工做,那張三就同時擁有行政和財務兩個角色的權限。

角色與權限是多對多關係,用戶與權限之間也是多對多關係,經過角色間接創建。

3張基礎表:用戶、角色、權限

2張中間表:創建用戶與角色的多對多關係,角色與權限的多對多關係。

DROP DATABASE IF EXISTS shiro;
CREATE DATABASE shiro DEFAULT CHARACTER SET utf8;
USE shiro;
 
DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS role;
DROP TABLE IF EXISTS permission;
DROP TABLE IF EXISTS user_role;
DROP TABLE IF EXISTS role_permission;

/*用戶表*/
CREATE TABLE `user` (
  id BIGINT AUTO_INCREMENT,
  NAME VARCHAR(100),
  PASSWORD VARCHAR(100),
  CONSTRAINT pk_users PRIMARY KEY(id)
) CHARSET=utf8 ENGINE=INNODB;

/*角色表*/
CREATE TABLE role (
  id BIGINT AUTO_INCREMENT,
  NAME VARCHAR(100),
  CONSTRAINT pk_roles PRIMARY KEY(id)
) CHARSET=utf8 ENGINE=INNODB;

/*權限表*/
CREATE TABLE permission (
  id BIGINT AUTO_INCREMENT,
  NAME VARCHAR(100),
  CONSTRAINT pk_permissions PRIMARY KEY(id)
) CHARSET=utf8 ENGINE=INNODB;

/*用戶角色(關係)表*/
CREATE TABLE user_role (
  uid BIGINT,
  rid BIGINT,
  CONSTRAINT pk_users_roles PRIMARY KEY(uid, rid)
) CHARSET=utf8 ENGINE=INNODB;

/*角色權限(關係)表*/
CREATE TABLE role_permission (
  rid BIGINT,
  pid BIGINT,
  CONSTRAINT pk_roles_permissions PRIMARY KEY(rid, pid)
) CHARSET=utf8 ENGINE=INNODB;

其餘模型這裏不作過多深究介紹,其餘模型的理解參考此篇文章:

http://www.woshipm.com/pd/1150093.html

三、權限(許可)

權限是資源的集合。

這裏的資源指的是軟件中全部的內容,包括模塊、菜單、頁面、字段、操做功能(增刪改查)等等。

具體的權限配置上,能夠將權限分爲:頁面權限、操做權限和數據權限

頁面權限:全部系統都是由一個個的頁面組成,頁面再組成模塊,用戶是否能看到這個頁面的菜單、是否能進入這個頁面就稱爲頁面權限。

操做權限:用戶凡是在操做系統中的任何動做、交互都是操做權限,如增刪改查等。

數據權限:通常業務管理系統,都有數據私密性的要求:哪些人能夠看到哪些數據,不能夠看到哪些數據。

4、與其相關的SpringSecurity框架

與Shiro相關的,可能就是SpringSecurity了

官網:https://spring.io/projects/spring-security

image
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

是一個認證和訪問控制(受權)框架,來保護基於Spring的應用程序,專一於爲java應用提供認證和受權。

SpringSecurity屬於Spring全家桶的一部分,對於Spring項目來講,其實使用它是討巧的。

關於Shiro和SpringSecurity的對比,筆者在網上查詢了下資料,並沒發現有講得很好的。

大多說的是因需使用,看使用場景,選擇使用。但在簡單性上,仍是優先選擇Shiro。本身對這兩大框架的應用也並無太多,所以也沒法講清。在此只是提一嘴SpringSecurity,感興趣的可自行對比研究。

5、第一個簡單純粹的Shiro程序

根據官網:
image

一、新建一個普通的Maven項目

二、導入對應的Pom依賴

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.1</version>
</dependency>
<!-- Shiro uses SLF4J for logging.  We'll use the 'simple' binding
             in this example app.  See http://www.slf4j.org for more info. -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.21</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.21</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

三、編寫Shiro配置

https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/resources/log4j.properties

log4j.properties:

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

shiro.ini:

[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# 
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5

四、編寫Quickstart.java

官網上有一個10分鐘教程,它讓咱們先看Quickstart.java學習
image

image

https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/java/Quickstart.java

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Simple Quickstart application showing how to use Shiro's API.
 * 簡單的快速啓動應用程序,演示如何使用Shiro的API。
 * @since 0.9 RC2
 */
public class Quickstart {

    //日誌門面,默認是commons-logging
    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityUtils.setSecurityManager(securityManager);
        
        // Now that a simple Shiro environment is set up, let's see what you can do:
        // get the currently executing user:
        //得到當前執行用戶(重要!!)
        Subject currentUser = SecurityUtils.getSubject();
        // Do some stuff with a Session (no need for a web or EJB container!!!)
        //不一樣於HttpSession,不須要Web或EJB的容器支持
        Session session = currentUser.getSession();
        //存值取值
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        //當前用戶身份驗證
        if (!currentUser.isAuthenticated()) {
            //建立標記,其中用戶名和密碼是讀取shiro.ini配置文件中的
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                //執行登陸操做!!!源碼中看不到,但就是這個操做!
                currentUser.login(token);
            } catch (UnknownAccountException uae) {//未知用戶異常(用戶不存在)
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {//密碼不正確
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {//如密碼輸入錯誤次數過多,鎖帳戶
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {//大異常,相似java中的Exception
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //粗粒度
        //test a typed permission (not instance-level)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //細粒度
        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //註銷
        //all done - log out!
        currentUser.logout();

        //結束系統
        System.exit(0);
    }
}

五、main方法啓動測試

運行結果:

打印了一堆默認的日誌消息

2020-09-19 13:09:07,652 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 
2020-09-19 13:09:07,733 INFO [Quickstart] - Retrieved the correct value! [aValue] 
2020-09-19 13:09:07,734 INFO [Quickstart] - User [lonestarr] logged in successfully. 
2020-09-19 13:09:07,734 INFO [Quickstart] - May the Schwartz be with you! 
2020-09-19 13:09:07,734 INFO [Quickstart] - You may use a lightsaber ring.  Use it wisely. 
2020-09-19 13:09:07,735 INFO [Quickstart] - You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  Here are the keys - have fun!
  • 若啓動報錯,Pom中嘗試導入後
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.2</version>
</dependency>
  • 若啓動後,什麼都沒打印,嘗試Pom依賴中刪掉全部的<scope>runtime</scope>做用域

六、加深理解

①類的描述

/**
* Simple Quickstart application showing how to use Shiro's API. 
* 簡單的快速啓動應用程序,演示如何使用Shiro的API。
*/

②經過工廠模式建立SecurityManager的實例對象

// 讀取類路徑下的shiro.ini文件
// Use the shiro.ini file at the root of the classpath
// (file: and url: prefixes load from files and urls respectively):
Factory<SecurityManager> factory = new
IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 已經創建了一個簡單的Shiro環境

③獲取當前的Subject

// get the currently executing user: 獲取當前正在執行的用戶
Subject currentUser = SecurityUtils.getSubject();

④session的操做

// 用會話作一些事情(不須要web或EJB容器!!!)
// Do some stuff with a Session (no need for a web or EJB container!!!)
//存值取值
Session session = currentUser.getSession(); //得到session
session.setAttribute("someKey", "aValue"); //設置Session的值!
String value = (String) session.getAttribute("someKey"); //從session中獲取
值
if (value.equals("aValue")) { //判斷session中是否存在這個值!
  log.info("==Retrieved the correct value! [" + value + "]");
}

⑤用戶認證

// 測試當前的用戶是否已經被認證,便是否已經登陸!
// let's login the current user so we can check against roles and
permissions:
if (!currentUser.isAuthenticated()) { // isAuthenticated();是否定證
    //將用戶名和密碼封裝爲 UsernamePasswordToken ;
    UsernamePasswordToken token = new UsernamePasswordToken("lonestarr",
                                                            "vespa");
    token.setRememberMe(true); //記住我功能
    try {
        currentUser.login(token); //執行登陸,能夠登陸成功的!
    } catch (UnknownAccountException uae) { 
        //若是沒有指定的用戶,則UnknownAccountException異常
        log.info("There is no user with username of " +
                     token.getPrincipal());
    } catch (IncorrectCredentialsException ice) { //密碼不正確的異常!
        log.info("Password for account " + token.getPrincipal() + " was
                 incorrect!");
    } catch (LockedAccountException lae) { //用戶被鎖定的異常
        log.info("The account for username " + token.getPrincipal() + "is locked. "              +"Please contact your administrator to unlock it.");
    }
    // ... catch more exceptions here (maybe custom ones specific toyour application?
    catch (AuthenticationException ae) { //認證異常,上面的異常都是它的子類
    //unexpected condition? error?
    }
}
   //             
   log.info("User [" + currentUser.getPrincipal() + "] logged insuccessfully.");

⑥角色檢查

//test a role:
//是否存在某一個角色
if (currentUser.hasRole("schwartz")) {
    log.info("May the Schwartz be with you!");
} else {
    log.info("Hello, mere mortal.");
}

⑦權限檢查,粗粒度

//測試用戶是否具備某一個權限,行爲
//test a typed permission (not instance-level)
if (currentUser.isPermitted("lightsaber:wield")) {
    log.info("You may use a lightsaber ring. Use it wisely.");
} else {
    log.info("Sorry, lightsaber rings are for schwartz masters only.");
}

⑧權限檢查,細粒度

//測試用戶是否具備某一個權限,行爲,比上面更加的具體!
//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
    log.info("You are permitted to 'drive' the winnebago with license
             plate (id) 'eagle5'. " +
             "Here are the keys - have fun!");
} else {
    log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}

⑨註銷操做

//執行註銷操做!
//all done - log out!
currentUser.logout();

⑩退出系統

System.exit(0);

6、Shiro的架構(三大核心組件)

image

Shiro 架構包含三個主要的理念:Subject、SecurityManagerRealm

  • Subject:表明當前用戶,Subject 能夠是一我的,但也能夠是第三方服務、守護進程賬戶、時鐘守護任務或者其它當前和軟件交互的任何事件(網絡爬蟲、機器人等)。Subject與應用代碼直接交互,也就是說Shiro的對外API核心就是Subject。SecurityManageer纔是實際的執行者。
  • SecurityManager:安全管理器,管理全部Subject,SecurityManager 是 Shiro 架構的核心,負責與Shiro的其餘組件進行交互,它至關於SpringMVC的DispatcherServlet的角色。
  • Realms:域,但這不容易理解。它用於進行權限信息的驗證,須要咱們本身實現(能夠用JDBC實現,也能夠是內存實現)。SecurityManager 要驗證用戶身份,那麼它須要從Realm 獲取相應的用戶進行比較,來肯定用戶的身份是否合法;也須要從Realm獲得用戶相應的角色、權限,進行驗證用戶的操做是否可以進行。因此Realm 本質上是一個特定的安全 DAO:它封裝與數據源鏈接的細節,獲得Shiro 所需的相關的數據。在配置 Shiro 的時候,你必須指定至少一個Realm 來實現認證(authentication)和/或受權(authorization)。

咱們須要實現Realms的Authentication 和 Authorization。其中 Authentication 是用來驗證用戶身份,Authorization 是受權訪問控制,用於對用戶進行的操做受權,證實該用戶是否容許進行當前操做,如訪問某個連接,某個資源文件等。

內部架構

image

  • Authenticator:負責Subject認證,是一個擴展點,能夠自定義實現;可使用認證策略
    (Authentication Strategy),即什麼狀況下算用戶認證經過了
  • Authorizer:受權器,即訪問控制器,用來決定主體是否有權限進行相應的操做;即控制着用戶能
    訪問應用中的那些功能
  • SessionManager:管理Session生命週期的組件,而Shiro並不只僅能夠用在Web環境,也能夠用
    在普通的JavaSE環境中
  • CacheManager:緩存控制器,來管理如用戶,角色,權限等緩存的;由於這些數據基本上不多改
    變,放到緩存中後能夠提升訪問的性能
  • Cryptography:密碼模塊,Shiro 提升了一些常見的加密組件用於密碼加密,解密等。

Shiro中其餘的一些概念:

  • Principals:通常指用戶名等,惟一代表Subject身份也就是當前用戶身份的東西;
  • Credentials:憑證,通常指密碼,對當前登錄用戶進行驗證;

7、Shiro集成SpringBoot和數據庫使用

場景任務:

  • 用戶訪問註冊頁面,輸入用戶名密碼,點擊提交註冊後,用shiro進行鹽值MD5加密後保存到數據庫——註冊
  • 用戶訪問登陸頁面,進行登陸時經過shiro進行用戶認證——登陸認證
  • 登陸成功則顯示當前登陸的用戶名(session管理)——登陸認證
  • 進入首頁,訪問其餘頁面須要認證登陸——頁面攔截登陸
  • 退出註銷session回到首頁——退出
  • 只有某個用戶具備訪問某個頁面的權限才能訪問——受權

第一部分:簡單環境搭建

一、建庫建表sql

DROP DATABASE IF EXISTS shiro;
CREATE DATABASE shiro DEFAULT CHARACTER SET utf8;
USE shiro;
/*用戶表*/
/*salt 是鹽,用來和 shiro 結合的時候,加密用的*/
CREATE TABLE `user`(
  id INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255)DEFAULT NULL,
  `password` VARCHAR(255)DEFAULT NULL,
  salt VARCHAR(255)DEFAULT NULL,
  PRIMARY KEY(id)
)ENGINE=INNODB DEFAULT CHARSET=utf8;

二、新建一個SpringBoot項目

勾選lombok、SpringWeb、thymeleaf、JDBC API以及MySQL Driver

三、導入Pom依賴,shiro兩種方式

第一種:

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

第二種:

<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>1.6.0</version>
</dependency>

其中第二種會報錯,在末尾第九部分會具體說明出現了什麼錯誤以及怎麼解決

mybatis依賴:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

四、配置application.yml文件

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    username: root
    password: admin
    
  thymeleaf:
    cache: false
    encoding: utf-8
    mode: HTML5
    servlet:
      content-type: text/html

mybatis:
  mapper-locations: classpath:/mapper/*.xml
  type-aliases-package: com.cqy.shiro.pojo
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

server:
  servlet:
    context-path: /shiro

五、編寫一個簡單的register.html註冊頁面(使用Vue)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>註冊</title>
    <script src="js/vue/2.5.16/vue.min.js"></script>
    <script src="js/axios/0.17.1/axios.min.js"></script>
<!--    <script src="js/jquery/2.0.0/jquery.min.js"></script>-->
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<script>
    $(function () {
        var data4 = {
            uri: 'submitregister',
            user: {
                name: '',
                password:''
            }
        };
        var vue = new Vue({
            el: '#work',
            data: data4,
            mounted: function(){

            },
            methods: {
                submit: function () {
                    var url = this.uri;
                    axios.post(url,this.user).then(function (response) {
                       //暫時不寫,後面寫了controller後再編寫
                    });
                }
            }
        });
    });
</script>

<div id="work">
    <h3>註冊</h3>
    用戶名:<input type="text" v-model="user.name"><br>
    密碼:<input type="password" v-model="user.password"><br>
    <button type="button" @click="submit">提交</button>
</div>
</body>
</html>

六、PageController用於頁面跳轉

register.html放在templates裏面,直接訪問不了,須要內部跳轉訪問

@Controller
public class PageController {

    @GetMapping("/register")
    public String register(){
        return "register";
    }
}

七、建立pojo,User類

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String password;
    private String salt;
}

八、建立UserMapper和UserMapper.xml

這裏爲了便捷,直接使用Class再也不進行dao、service層的編寫

提供兩個方法

@Repository
public interface UserMapper {
    //根據name查詢用戶
    public User queryUserByName(String name);
    //添加用戶
    public int addUser(User user);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cqy.shiro.mapper.UserMapper">

    <select id="queryUserByName" resultType="user">
        select * from `user` where `name`=#{name}
    </select>

    <insert id="addUser" parameterType="user">
        insert into `user` values (#{id},#{name},#{password},#{salt})
    </insert>

</mapper>

九、建立一個login.html登陸頁面用於登陸

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>登陸</title>
    <script src="js/vue/2.5.16/vue.min.js"></script>
    <script src="js/axios/0.17.1/axios.min.js"></script>
    <!--    <script src="js/jquery/2.0.0/jquery.min.js"></script>-->
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<script>
    $(function () {
        var data4 = {
            uri: 'submitlogin',
            user: {
                name: '',
                password:''
            }
        };
        var vue = new Vue({
            el: '#work',
            data: data4,
            methods: {
                submit: function () {
                    var url = this.uri;
                    axios.post(url,this.user).then(function (response) {
                        //暫時不寫,後面寫了controller後再編寫
                    });
                }
            }
        });
    });
</script>
<div id="work">
    <h3>登陸</h3>
    用戶名:<input type="text" v-model="user.name"><br>
    密碼:<input type="password" v-model="user.password"><br>
    <button type="button" @click="submit">登陸</button>
</div>
</body>
</html>

第二部分:進行Shiro相關的配置

一、導入Shiro的Pom依賴(文上)

二、自定義一個 Realm 的類

用來編寫一些認證與受權的邏輯

//自定義Realm
public class UserRealm extends AuthorizingRealm {
    
    //執行受權邏輯
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //執行時機:在用戶進行登陸,認證身份時執行
        System.out.println("====>>>>執行了受權邏輯PrincipalCollection");
        return null;
    }

    //執行認證邏輯
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //執行時機:在用戶登陸後,每訪問一次那些須要權限才能訪問的頁面時執行
        System.out.println("====>>>>執行了認證邏輯AuthenticationToken");
        //暫時爲空,後面根據業務邏輯編寫認證代碼
        return null;
    }
}

三、編寫Shiro配置類,config包下

@Configuration
public class ShiroConfig {

    //建立ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //設置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        return shiroFilterFactoryBean;
    }

    //建立DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getUserRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //關聯Realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

     //建立Hash憑證匹配器
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //md5默認就是加密1次
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(1);
        return hashedCredentialsMatcher;
    }
    
    //建立Realm對象,須要自定義
    @Bean
    public UserRealm getUserRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher hashedCredentialsMatcher){
        UserRealm userRealm = new UserRealm();
        //關聯憑證匹配器!!!
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return userRealm;
    }
}

第三部分:編寫業務邏輯

註冊:

一、編寫註冊的Controller

@RestController
public class ShiroController {

    @Autowired
    UserMapper userMapper;

    @PostMapping("/submitregister")
    public Object submitRegister(@RequestBody User user){
        String name = user.getName();
        String password = user.getPassword();
        //根據name從數據庫中查詢對應的user
        if (null!=userMapper.queryUserByName(name)){
            return "用戶名已經被使用,請從新輸入";
        }
        //經過隨機方式建立鹽,而且加密算法採用md5
        String salt = new SecureRandomNumberGenerator().nextBytes().toString();
        //加密次數,默認加密1次
        int times = 1;
        String algorithmName = "md5";
        String encodePassword = new SimpleHash(algorithmName,password,salt,times).toString();
        //鹽若是丟失了,沒法驗證密碼是否正確,所以會添加到數據庫保存
        user.setSalt(salt);
        user.setPassword(encodePassword);
        //添加到數據庫
        userMapper.addUser(user);
        return 0;
    }
}

二、register.html修改

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>註冊</title>
    <script src="js/vue/2.5.16/vue.min.js"></script>
    <script src="js/axios/0.17.1/axios.min.js"></script>
<!--    <script src="js/jquery/2.0.0/jquery.min.js"></script>-->
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<script>
    $(function () {
        var data4 = {
            uri: 'register',
            user: {
                name: '',
                password:''
            }
        };
        var vue = new Vue({
            el: '#work',
            data: data4,
            mounted: function(){

            },
            methods: {
                submit: function () {
                    var url = this.uri;
                    axios.post(url,this.user).then(function (response) {
                        var result = response.data;
                        //若是註冊成功,跳轉到註冊成功頁面
                        if (0===result){
                            location.href="registerSuccess";
                        }
                    });
                }
            }
        });
    });
</script>

<div id="work">
    <h3>註冊</h3>
    用戶名:<input type="text" v-model="user.name"><br>
    密碼:<input type="password" v-model="user.password"><br>
    <button type="button" @click="submit">提交</button>
</div>
</body>
</html>

三、建立registerSuccess.html註冊成功頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>註冊成功頁面</title>
</head>
<body>
<h1>恭喜您,註冊成功!</h1>
</body>
</html>

四、PageController中添加跳轉

@GetMapping("/registerSuccess")
public String registerSuccess(){
    return "registerSuccess";
}

五、啓動SpringBoot項目

訪問http://localhost:8080/shiro/register

輸入用戶名和密碼,點擊提交進行註冊

image
發現頁面跳轉到註冊成功頁面,查看數據庫user表

image

加密註冊成功!

登陸認證:

一、編寫Realm裏的認證邏輯

@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //將AuthenticationToken轉成UsernamePasswordToken
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //獲取帳號名
        String username = token.getUsername();
        //根據name獲取用戶對象從而拿到密碼和鹽值
        User user = userMapper.queryUserByName(username);
        //獲取用戶密碼(數據庫中加密後的),須要經過user對象來拿
        String passwordInDB = user.getPassword();
        //拿到鹽
        String salt = user.getSalt();
        //認證信息裏存放帳號密碼,getName()是當前Realm的繼承方法,一般返回當前類名:UserRealm
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, passwordInDB, ByteSource.Util.bytes(salt), getName());
        return simpleAuthenticationInfo;
    }

二、編寫登陸的Controller,在ShiroController中

@PostMapping("/submitlogin")
    public Object submitLogin(@RequestBody User userParam){
        String name = userParam.getName();
        String password = userParam.getPassword();
        //使用Shiro方式獲取當前用戶
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(name, password);
        //執行登陸,與Realm中查詢的數據庫中用戶的信息進行比較
        try {
            subject.login(token);
            //根據用戶名
            User user = userMapper.queryUserByName(name);
            subject.getSession().setAttribute("user",user);
            return 0;
        } catch (AuthenticationException e) {
            return "帳號或密碼錯誤";
        }
    }

三、login.html修改

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>登陸</title>
    <script src="js/vue/2.5.16/vue.min.js"></script>
    <script src="js/axios/0.17.1/axios.min.js"></script>
    <!--    <script src="js/jquery/2.0.0/jquery.min.js"></script>-->
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<script>
    $(function () {
        var data4 = {
            uri: 'submitlogin',
            user: {
                name: '',
                password:''
            }
        };
        var vue = new Vue({
            el: '#work',
            data: data4,
            methods: {
                submit: function () {
                    var url = this.uri;
                    axios.post(url,this.user).then(function (response) {
                        var result = response.data;
                        //若是返回0,說明認證成功,即跳轉到登錄成功頁面
                        if (0===result){
                            location.href="loginSuccess";
                        }
                    });
                }
            }
        });
    });
</script>
<div id="work">
    <h3>登陸</h3>
    用戶名:<input type="text" v-model="user.name"><br>
    密碼:<input type="password" v-model="user.password"><br>
    <button type="button" @click="submit">登陸</button>
</div>
</body>
</html>

四、建立loginSuccess.html登陸成功頁面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登陸成功頁面</title>
</head>
<body>
<h1>恭喜您登陸成功!</h1>
<span>當前用戶是:
<a th:text="${session.user.name}"></a>
</span>
</body>
</html>

五、PageController中添加跳轉

@GetMapping("/loginSuccess")
public String loginSuccess(){
    return "loginSuccess";
}

六、啓動SpringBoot項目

訪問http://localhost:8080/shiro/login

輸入用戶名密碼,點擊登陸

發現頁面跳轉到登陸成功頁面,並顯示出了當前登陸用戶:紙飛機

image

頁面登陸攔截:

一、在templates目錄下新建一個user目錄,編寫兩個頁面add.html、update.html

<body>
<h1>add</h1>
</body>
<body>
<h1>update</h1>
</body>

二、編寫對應跳轉到兩個頁面的Controller

@GetMapping("/user/add")
public String toAdd(){
    return "user/add";
}

@GetMapping("/user/update")
public String toUpdate(){
    return "user/update";
}

三、在templates目錄下建立一個首頁index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首頁</title>
</head>
<body>
    <a href="login">點擊進入登陸頁面</a>
    <br>
    <br>
    <a th:href="@{/user/add}">add</a> | 
    <a th:href="@{/user/update}">update</a>
</body>
</html>

四、跳轉到首頁index.html的Controller

@GetMapping({"/","/index"})
public String toIndex(){
    return "index";
}

五、運行測試頁面跳轉是否OK(能夠直接進入add或update頁面)

六、準備編寫Shiro的內置過濾器(設置過濾規則)

//建立ShiroFilterFactoryBean
    @Bean(name ="shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //設置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        //修改到要跳轉的login頁面;
        shiroFilterFactoryBean.setLoginUrl("/login");
        /*
         * 經常使用的有以下過濾器:
         * anon: 無需認證就能夠訪問
         * authc: 必須認證才能夠訪問
         * user: 若是使用了記住我功能就能夠直接訪問
         * perms: 擁有某個資源權限才能夠訪問
         * role: 擁有某個角色權限才能夠訪問
         */
        //注意此處使用的是LinkedHashMap,是有順序的,shiro會按從上到下的順序匹配驗證,匹配了就再也不繼續驗證
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/add","authc");
        filterMap.put("/user/update","authc");
        //能夠直接使通配符
        //filterMap.put("/user/*","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

七、啓動測試訪問,發現點擊add|update連接沒法進入,已經被攔截,會自動給咱們跳轉到login.jsp頁面。咱們須要自定義跳轉頁面。

image

八、在ShiroFilterFactoryBean方法中添加shiroFilterFactoryBean.setLoginUrl

//修改到要跳轉的login頁面;
//前面咱們已經建立好了login.html頁面和跳轉的Controller
shiroFilterFactoryBean.setLoginUrl("/login");

九、再次啓動測試,發現點擊add|update連接會直接跳轉到login.html登陸頁面讓你進行登陸

image

退出:

一、在登陸成功頁面loginSuccess添加退出的連接

<a href="logout">退出</a>

二、編寫退出的Controller

//退出登陸,跳轉到首頁
@GetMapping("/logout")
public String loginOut() {
    Subject subject = SecurityUtils.getSubject();
    if(subject.isAuthenticated())
        subject.logout();//會自動讓session失效
    return "redirect:index";
}

受權:

簡單Shiro的過濾器攔截請求

一、在登陸成功頁面loginSuccess添加跳轉到首頁index.html的連接

<a href="index">點擊跳轉到首頁</a>

二、在ShiroFilterFactoryBean中添加

//受權攔截,須要放在認證的上面。某個用戶有add的權限才能夠訪問。
filterMap.put("/user/add","perms[user:add]");

三、啓動測試,登陸訪問add,發現出現401錯誤,Unauthorized未受權

image

當咱們實現權限攔截後,shiro會自動跳轉到未受權的頁面,但咱們沒有這個頁面,因此401

注意:ShiroFilterFactoryBean中完成受權配置後,此處訪問add必需要先登陸,未登陸的狀況下,受權的perms參數也會直接跳轉到login,要求你先登陸。

四、配置一個未受權的提示的頁面,增長一個controller提示。而後在ShiroFilterFactoryBean中添加配置

@RequestMapping("/noauth")
@ResponseBody
public String noAuth(){
    return "未經受權不能訪問此頁面";
}
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");

五、再次啓動訪問測試。攔截成功。

image

六、ShiroFilterFactoryBean中完整代碼

//建立ShiroFilterFactoryBean
    @Bean(name ="shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //設置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        /*
         * 經常使用的有以下過濾器:
         * anon: 無需認證就能夠訪問
         * authc: 必須認證才能夠訪問
         * user: 若是使用了記住我功能就能夠直接訪問
         * perms: 擁有某個資源權限才能夠訪問
         * role: 擁有某個角色權限才能夠訪問
         */
        //注意此處使用的是LinkedHashMap,是有順序的,shiro會按從上到下的順序匹配驗證,匹配了就再也不繼續驗證
        //因此上面的url要苛刻,寬鬆的url要放在下面,尤爲是"/**"要放到最下面,若是放前面的話其後的驗證規則就沒做用了。
        Map<String, String> filterMap = new LinkedHashMap<>();
        //受權攔截,須要放在認證的上面。某個用戶有add的權限才能夠訪問。
        //受權,正常狀況下,沒有受權會跳轉到未受權頁面
        filterMap.put("/user/update","perms[user:update]");
        filterMap.put("/user/add","perms[user:add]");

        //認證
//        filterMap.put("/user/add","authc");
//        filterMap.put("/user/update","authc");

        //修改到要跳轉的login頁面;
        shiroFilterFactoryBean.setLoginUrl("/login");
        //跳轉到未受權的請求頁面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

而如何給某個用戶具體受權,進行權限的操做等。本文不作細緻的講解,由於這須要結合項目中具體的業務邏輯來進行編寫,而且須要進行數據庫權限表等一系列的設計操做等,相較複雜。

8、使用Shiro的坑

在文章集成SpringBoot部分,說了Shiro的導入Pom依賴的兩種方式。其中第二種可能會報錯。

報錯內容一:

可能會出現啓動時沒法找到shiroFilterFactoryBean的bean

***************************
APPLICATION FAILED TO START
***************************

Description:

Method filterShiroFilterRegistrationBean in org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration required a bean named 'shiroFilterFactoryBean' that could not be found.


Action:

Consider defining a bean named 'shiroFilterFactoryBean' in your configuration.

解決:若是在Shiro配置類中的ShiroFilterFactoryBean的方法名沒有命名爲shiroFilterFactoryBean,須要手動在@Bean上添加上名字,不然沒法識別注入。以下

@Bean(name = "shiroFilterFactoryBean")

報錯內容二:

項目沒法啓動,報沒有authorizer的bean的錯誤: No bean named 'authorizer' available

解決:本身在配置類中配置

@Configuration
public class ShiroConfig {

@Bean
public Authorizer authorizer(){
    return new ModularRealmAuthorizer();
    }
}

具體參考這篇文章:https://www.cnblogs.com/insan...

9、總結

本文只是Shiro的入門學習,適合於新手初探Shiro,有個大體的瞭解與應用。

主要講了一些Shiro的基礎知識,以及集成SpringBoot簡單應用的流程和配置,而對於Shiro的一些源碼等內容並無進行一個分析和講解,可能會在以後的進階文章中補充。

學習建議:

一、Shiro的一些配置類和簡單業務邏輯代碼是死的,大可不用花不少精力去死記那些單詞又長的代碼,在初學以後應總結成本身的環境模板,用時即取便可,多將精力放在Shiro的整個認證受權的運行過程上,瞭解是怎麼進行的。

二、Shiro可定製化程度很高,在不少涉及到權限的項目中都有運用,可自行去碼雲或GitHub上尋找優質的(star數較高)的開源項目(如權限管理系統)來進行學習,看實際的項目中Shiro的應用。

三、本文文中涉及的代碼。須要的話自行下載:連接:https://pan.baidu.com/s/1xicF...
提取碼:quo0


參考:

一、http://shiro.apache.org/index... Shiro官網

二、https://www.bilibili.com/vide... 碰見狂神說,【狂神說Java】SpringBoot整合Shiro框架

三、https://zhuanlan.zhihu.com/p/... 我沒有三顆心臟,Shiro安全框架【快速入門】就這一篇!公衆號wmyskxz

四、https://juejin.im/post/684490... 碼農小胖哥,Spring Security 實戰乾貨: RBAC權限控制概念的理解

五、http://www.woshipm.com/pd/115... 珣玗琪,RBAC模型:基於用戶-角色-權限控制的一些思考

六、https://how2j.cn/k/shiro/shir... How2j Shiro系列教材


謝謝您看完這篇技術文章

若是能對您有所幫助

那將是一件很美好的事情

保持好奇心的終身學習也是極棒的事

願世界簡單又多彩

轉載請註明出處

​ ——紙飛機

相關文章
相關標籤/搜索