中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Shiro認證與授權原理是什么

發布時間:2021-12-18 11:24:53 來源:億速云 閱讀:131 作者:iii 欄目:互聯網科技

本篇內容介紹了“Shiro認證與授權原理是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

前言

Shiro作為常用的權限框架,可被用于解決認證、授權、加密、會話管理等場景。Shiro對其API進行了友好的封裝,如果單純的使用Shiro框架非常簡單。但如果使用了多年Shiro,還依舊停留在基本的使用上,那么這篇文章就值得你學習一下。只有了解Shiro的底層和實現,才能夠更好的使用和借鑒,同時也能夠避免不必要的坑。

下面以官方提供的實例為基礎,講解分析Shiro的基本使用流程,同時針對認證和授權流程進行更底層的原理講解,讓大家真正了解我們所使用的Shiro框架,底層是怎么運作的。

Shiro組成及框架

在學習Shiro各個功能模塊之前,需要先從整體上了解Shiro的整體架構,以及核心組件所處的位置。下面為官方提供的架構圖:

Shiro認證與授權原理是什么

上圖可以看出Security Manager是Shiro的核心,無論認證、授權、會話管理等都是通過它來進行管理的。在使用和分析原理之前,先來了解后面會用到的組件及其功能:

  • Subject:主體,可以是用戶或程序,主體可以訪問Security Manager以獲得認證、授權、會話等服務;

  • Security Manager:安全管理器,主體所需的認證、授權功能都是在這里進行的,是Shiro的核心;

  • Authenticator:認證器,主體的認證過程通過Authenticator進行;

  • Authorizer:授權器,主體的授權過程通過Authorizer進行;

  • Session Manager:shiro的會話管理器,與web應用提供的Session管理分隔開;

  • Realm:域,可以有一個或多個域,可通過Realm存儲授權和認證的邏輯;

上面只列出了部分組件及功能,其他更多組件在后續文章會逐步為大家實踐講解。了解了這些組件和核心功能之后,下面以官方的示例進行講解。

官方實例分析

Shiro官方示例地址為:http://shiro.apache.org/tutorial.html ,需要留意的是官方示例已經有些老了,在實踐中會做一些調整。

我們先在本地將環境搭建起來,運行程序。創建一個基于Maven的Java項目,引入如下依賴:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.7.0</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.29</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.29</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

目前最新版本為1.7.0,可根據需要引入其他版本。運行官方實例比較坑的地方是,還需要引入log4j和slf4j對應的依賴。都是Apache的項目,因此底層默認采用了log4j的日志框架,如果不引入對應的日志依賴,會報錯或無法打印日志。

緊接著,在resources目錄下創建一個shiro.ini文件,將官網提供內容配置內容復制進去:

# =============================================================================
# Tutorial INI configuration
#
# Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :)
# =============================================================================

# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5

這個文件可看成是一個Realm,其實就是shiro默認的IniRealm,當然在不同的項目中用戶、權限、角色等信息可以以各種形式存儲,比如數據庫存儲、緩存存儲等。

上述配置文件格式的語義也比較明確,配置了用戶和角色等信息,大家留意看一下注釋中對數據格式的解釋。root = secret, admin表示用戶名root,密碼是secret,角色是admin。其中角色可以配置多個,在后面依次用逗號分隔即可。schwartz = lightsaber:*表示角色schwartz擁有權限lightsaber:*。

繼續創建一個Tutorial類,將官網提供的代碼復制進去,由于采用的是1.7.0版本,官網實例中下面的代碼已經沒辦法正常運行了:

Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);

原因是IniSecurityManagerFactory類已經被標注廢棄了,替代它的是Environment接口及其實現類。因此需將上述獲取SecurityManager的方式改為通過shiro提供的Environment來初始化和獲取:

Environment environment = new BasicIniEnvironment("classpath:shiro.ini");
SecurityManager securityManager = environment.getSecurityManager();
SecurityUtils.setSecurityManager(securityManager);

改造之后的完整代碼如下(其中英文注釋已翻譯成中文注釋):

public class Tutorial {

   private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);

   public static void main(String[] args) {
       log.info("My First Apache Shiro Application");

       // 1.初始化環境,主要是加載shiro.ini配置文件的信息
       Environment environment = new BasicIniEnvironment("classpath:shiro.ini");
      // 2.獲取SecurityManager安全管理器
      SecurityManager securityManager = environment.getSecurityManager();
      SecurityUtils.setSecurityManager(securityManager);

      // 3.獲取當前主體(用戶)
      Subject currentUser = SecurityUtils.getSubject();

      // 4.獲取當前主體的會話
      Session session = currentUser.getSession();
      // 5.向會話中存儲一些內容(不需要web容器或EJB容器)
      session.setAttribute("someKey", "aValue");
      // 6.再次從會話中獲取存儲的內容,并比較與存儲值是否一致。
      String value = (String) session.getAttribute("someKey");
      if ("aValue".equals(value)) {
         log.info("Retrieved the correct value! [" + value + "]");
      }

      // 當前用戶進行登錄操作,進而可以檢驗用戶的角色和權限。    
      // 7.判斷當前用戶是否認證(此時很顯然未認證)
      if (!currentUser.isAuthenticated()) {
         // 8.將賬號和密碼封裝到UsernamePasswordToken中
         UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
         // 9.記住我
         token.setRememberMe(true);
         try {
            // 10.進行登錄操作
            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 (AuthenticationException ae) {
            // 其他意外異常、error處理
         }
      }

      // 打印當前用戶的主體信息
      log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

      // 10.檢查是否有指定角色權限(前面已經通過Environment加載了權限和角色信息)
      if (currentUser.hasRole("schwartz")) {
         log.info("May the Schwartz be with you!");
      } else {
         log.info("Hello, mere mortal.");
      }

      // 判斷是否有資源操作權限
      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.");
      }

      // 更強級別的權限驗證
      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!");
      }

      // 11.登出
      currentUser.logout();
      System.exit(0);
   }
}

完整項目源碼:https://github.com/secbr/shiro

執行程序,打印日志信息如下,可以看到每一步的執行輸出:

INFO - My First Apache Shiro Application
INFO - Enabling session validation scheduler...
INFO - Retrieved the correct value! [aValue]
INFO - User [lonestarr] logged in successfully.
INFO - May the Schwartz be with you!
INFO - You may use a lightsaber ring.  Use it wisely.
INFO - You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  Here are the keys - have fun!

上述代碼中包含了11個主要的流程:

  • 1、初始化環境,這里主要是加載shiro.ini配置文件的信息;

  • 2、獲取SecurityManager安全管理器;

  • 3、獲取當前主體(用戶);

  • 4、獲取當前主體的會話;

  • 5、向會話中存儲一些內容(不需要web容器或EJB容器);

  • 6、再次從會話中獲取存儲的內容,并比較與存儲值是否一致;

  • 7、判斷當前用戶是否認證;

  • 8、將賬號和密碼封裝到UsernamePasswordToken中;

  • 9、開啟記住我;

  • 10、檢查是否有指定角色權限;

  • 11、退出登錄。

下面我們對幾個核心步驟步驟進行分析說明。

初始化環境

源碼中通過Environment對象來加載配置文件和初始化SecurityManager,然后通過工具類SecurityUtils對SecurityManager進行設置。在實踐中,可根據具體情況進行初始化,比如實例中通過Environment加載文件,也可以直接創建DefaultSecurityManager,在web項目采用DefaultWebSecurityManager等。

// 1.初始化環境,主要是加載shiro.ini配置文件的信息
Environment environment = new BasicIniEnvironment("classpath:shiro.ini");
// 2.獲取SecurityManager安全管理器
SecurityManager securityManager = environment.getSecurityManager();
SecurityUtils.setSecurityManager(securityManager);

這里的配置文件相當于一個Realm,部分SecurityManager實現類(比如:DefaultSecurityManager)提供了setRealm方法,用戶可通過該方法自定義設置Realm。

總之,無論獲取SecurityManager的方式如何,都需要有這么一個SecurityManager用來處理后續的認證、授權等處理,可見SecurityManager的核心地位。

認證流程

在上述實例代碼中,先將認證功能相關的核心代碼抽離出來,包含以下代碼及操作步驟(省略了SecurityManager的創建和設置):

// 獲取當前主體(用戶)
Subject currentUser = SecurityUtils.getSubject();
// 判斷當前用戶是否認證
currentUser.isAuthenticated()
// 將賬號和密碼封裝到UsernamePasswordToken中
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
// 記住我
token.setRememberMe(true);
// 登錄操作
currentUser.login(token);

從這個代碼流程上來看,Shiro的認證過程包括:初始化環境,獲取當前用戶主體,判斷是否認證過,將賬號密碼進行封裝,進行認證,認證完成校驗權限。可以通過下圖來表示整個流程。
Shiro認證與授權原理是什么

接下來,通過跟蹤源碼,來看看Shiro的認證流程涉及到哪些組件。

認證原理分析

認證的入口程序是login方法,以此方法為入口,進行跟蹤,并忽略掉非核心操作,可得出認證邏輯經過以下代碼執行步驟:

//currentUser類型為Subject,在構造了SecurityManager之后,提交認證,token封裝了用戶信息
currentUser.login(token);

//DelegatingSubject類中,調用SecurityManager執行認證
Subject subject = this.securityManager.login(this, token);

//DefaultSecurityManager類中,SecurityManager委托給Authenticator執行認證邏輯
AuthenticationInfo info = this.authenticate(token);

// AuthenticatingSecurityManager類中,進行認證
this.authenticator.authenticate(token);

//AbstractAuthenticator類中,進行認證
AuthenticationInfo info = this.doAuthenticate(token);

//ModularRealmAuthenticator類中,獲取多Realm進行身份認證
Collection<Realm> realms = this.getRealms();
doSingleRealmAuthentication(realm, token);

//ModularRealmAuthenticator類中,針對具體的Realm進行身份認證
AuthenticationInfo info = realm.getAuthenticationInfo(token);

//AuthenticatingRealm類中,調用對應的Realm進行校驗,認證成功則返回用戶屬性
AuthenticationInfo info = realm.doGetAuthenticationInfo(token);

//SimpleAccountRealm類中,根據token獲取賬戶信息
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
SimpleAccount account = getUser(upToken.getUsername());

//AuthenticatingRealm類中,比對傳入的token和根據token獲取到的賬戶信息
assertCredentialsMatch(token, info);
->getCredentialsMatcher().doCredentialsMatch(token, info);

//SimpleCredentialsMatcher類中,進行具體對比
byte[] tokenBytes = toBytes(tokenCredentials);
byte[] accountBytes = toBytes(accountCredentials);
MessageDigest.isEqual(tokenBytes, accountBytes);
//或
accountCredentials.equals(tokenCredentials);

上述代碼包括了認證過程中一些核心流程,抽離出核心部分,整理成流程圖如下:

Shiro認證與授權原理是什么

可以看出,整個認證過程中涉及到了SecurityManager、Subject、Authenticator、Realm等組件,相關組件的功能可參考架構圖中的功能說明。

授權原理

實例中授權調用的代碼比較少,主要就是以下幾個方法:

// 檢查是否有相應角色權限
currentUser.hasRole("schwartz")
// 判斷是否有資源操作權限
currentUser.isPermitted("lightsaber:wield")
// 判斷是否有(更細粒度的)資源操作權限
currentUser.isPermitted("winnebago:drive:eagle5")

下面以hasRole方法為例,進行追蹤分析源代碼,看看具體的實現原理。

// 檢查是否有相應角色權限
currentUser.hasRole("schwartz");

// DelegatingSubject類中,委托給SecurityManager判斷角色與既定角色是否匹配
this.securityManager.hasRole(this.getPrincipals(), roleIdentifier);

// AuthorizingSecurityManager類中,SecurityManager委托Authorizer進行角色檢驗
this.authorizer.hasRole(principals, roleIdentifier);

// ModularRealmAuthorizer類中,獲取所有Realm,并遍歷檢查角色
for (Realm realm : getRealms());
((Authorizer) realm).hasRole(principals, roleIdentifier)

// AuthorizingRealm中,Authorizer判斷Realm中的角色/權限是否和傳入的匹配
AuthorizationInfo info = getAuthorizationInfo(principal);

// AuthorizingRealm中,執行Realm進行授權操作
AuthorizationInfo info = this.doGetAuthorizationInfo(principals);

// SimpleAccountRealm類中,獲得用戶SimpleAccount(實現了AuthorizationInfo),
// users類型為Map,以用戶名為key,對應shiro.ini中配置的初始化用戶信息
return this.users.get(username);

// AuthorizingRealm類中,判斷傳入的用戶和初始化配置的是否匹配
return hasRole(roleIdentifier, info);

// AuthorizingRealm類中,最終的授權判斷
return info != null && info.getRoles() != null && info.getRoles().contains(roleIdentifier);

上述代碼包括了授權過程中一些核心流程,抽離出核心部分,整理成流程圖(isPermitted方法類似,讀者可自行追蹤),如下:
Shiro認證與授權原理是什么

可以看出,整個認證過程中涉及到了SecurityManager、Subject、Authorizer、Realm等組件,相關組件的功能可參考架構圖中的功能說明。

自定義Realm

通過上面認證和授權流程及原理的分析,會發現無論哪個操作都需要通過Realm來定義用戶認證時需要的賬戶信息和授權時的權限信息。但一般情況下不會使用官網示例的基于“ini配置文件”的方式,而是通過自定義Realm組件來實現。

以下面的示例來說,我們可以使用Shiro內置的Realm組件:

public class AuthenticationTest {

    SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();

    @Before
    public void addUser() {
        // 在方法開始前添加一個用戶
        simpleAccountRealm.addAccount("wmyskxz", "123456");
    }

    @Test
    public void testAuthentication() {
        // 1.構建SecurityManager環境
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        defaultSecurityManager.setRealm(simpleAccountRealm);

        // 2.主體提交認證請求
        // 設置SecurityManager環境
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        // 獲取當前主體
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("wmyskxz", "123456");
        // 登錄
        subject.login(token);
        // subject.isAuthenticated()方法返回一個boolean值,用于判斷用戶是否認證成功
        System.out.println("isAuthenticated:" + subject.isAuthenticated());
    }
}

上述示例中創建了一個SimpleAccountRealm對象,并把初始化的賬戶信息通過addAccount方法添加進去。

實踐中自定義Realm的方法通常是繼承AuthorizingRealm類,并實現其doGetAuthorizationInfo方法和doGetAuthenticationInfo方法。在上面的流程梳理過程中,我們已經知道doGetAuthorizationInfo方法為授權功能的實現,而doGetAuthenticationInfo方法為認證的功能實現。關于具體實例,后續會用專門的實例來講解。

“Shiro認證與授權原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

客服| 伊春市| 衡东县| 汉沽区| 樟树市| 犍为县| 黎城县| 陈巴尔虎旗| 乌什县| 汶川县| 洛阳市| 叶城县| 昂仁县| 福海县| 台北县| 通许县| 屯留县| 麻江县| 神池县| 大竹县| 新民市| 阳新县| 晋州市| 宁国市| 廊坊市| 调兵山市| 香格里拉县| 安岳县| 大田县| 湘西| 光山县| 松滋市| 浦东新区| 澄城县| 黎平县| 道孚县| 梅州市| 漳州市| 津南区| 白河县| 泾川县|