溫馨提示×

溫馨提示×

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

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

Shiro認(rèn)證與授權(quán)原理是什么

發(fā)布時間:2021-12-18 11:24:53 來源:億速云 閱讀:131 作者:iii 欄目:互聯(lián)網(wǎng)科技

本篇內(nèi)容介紹了“Shiro認(rèn)證與授權(quán)原理是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

前言

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

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

Shiro組成及框架

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

Shiro認(rèn)證與授權(quán)原理是什么

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

  • Subject:主體,可以是用戶或程序,主體可以訪問Security Manager以獲得認(rèn)證、授權(quán)、會話等服務(wù);

  • Security Manager:安全管理器,主體所需的認(rèn)證、授權(quán)功能都是在這里進(jìn)行的,是Shiro的核心;

  • Authenticator:認(rèn)證器,主體的認(rèn)證過程通過Authenticator進(jìn)行;

  • Authorizer:授權(quán)器,主體的授權(quán)過程通過Authorizer進(jìn)行;

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

  • Realm:域,可以有一個或多個域,可通過Realm存儲授權(quán)和認(rèn)證的邏輯;

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

官方實例分析

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

我們先在本地將環(huán)境搭建起來,運行程序。創(chuàng)建一個基于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,可根據(jù)需要引入其他版本。運行官方實例比較坑的地方是,還需要引入log4j和slf4j對應(yīng)的依賴。都是Apache的項目,因此底層默認(rèn)采用了log4j的日志框架,如果不引入對應(yīng)的日志依賴,會報錯或無法打印日志。

緊接著,在resources目錄下創(chuàng)建一個shiro.ini文件,將官網(wǎng)提供內(nèi)容配置內(nèi)容復(fù)制進(jìn)去:

# =============================================================================
# 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默認(rèn)的IniRealm,當(dāng)然在不同的項目中用戶、權(quán)限、角色等信息可以以各種形式存儲,比如數(shù)據(jù)庫存儲、緩存存儲等。

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

繼續(xù)創(chuàng)建一個Tutorial類,將官網(wǎng)提供的代碼復(fù)制進(jìn)去,由于采用的是1.7.0版本,官網(wǎng)實例中下面的代碼已經(jīng)沒辦法正常運行了:

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

原因是IniSecurityManagerFactory類已經(jīng)被標(biāo)注廢棄了,替代它的是Environment接口及其實現(xiàn)類。因此需將上述獲取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.初始化環(huán)境,主要是加載shiro.ini配置文件的信息
       Environment environment = new BasicIniEnvironment("classpath:shiro.ini");
      // 2.獲取SecurityManager安全管理器
      SecurityManager securityManager = environment.getSecurityManager();
      SecurityUtils.setSecurityManager(securityManager);

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

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

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

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

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

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

      // 更強(qiáng)級別的權(quán)限驗證
      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

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

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、初始化環(huán)境,這里主要是加載shiro.ini配置文件的信息;

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

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

  • 4、獲取當(dāng)前主體的會話;

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

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

  • 7、判斷當(dāng)前用戶是否認(rèn)證;

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

  • 9、開啟記住我;

  • 10、檢查是否有指定角色權(quán)限;

  • 11、退出登錄。

下面我們對幾個核心步驟步驟進(jìn)行分析說明。

初始化環(huán)境

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

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

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

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

認(rèn)證流程

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

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

從這個代碼流程上來看,Shiro的認(rèn)證過程包括:初始化環(huán)境,獲取當(dāng)前用戶主體,判斷是否認(rèn)證過,將賬號密碼進(jìn)行封裝,進(jìn)行認(rèn)證,認(rèn)證完成校驗權(quán)限??梢酝ㄟ^下圖來表示整個流程。
Shiro認(rèn)證與授權(quán)原理是什么

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

認(rèn)證原理分析

認(rèn)證的入口程序是login方法,以此方法為入口,進(jìn)行跟蹤,并忽略掉非核心操作,可得出認(rèn)證邏輯經(jīng)過以下代碼執(zhí)行步驟:

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

//DelegatingSubject類中,調(diào)用SecurityManager執(zhí)行認(rèn)證
Subject subject = this.securityManager.login(this, token);

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

// AuthenticatingSecurityManager類中,進(jìn)行認(rèn)證
this.authenticator.authenticate(token);

//AbstractAuthenticator類中,進(jìn)行認(rèn)證
AuthenticationInfo info = this.doAuthenticate(token);

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

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

//AuthenticatingRealm類中,調(diào)用對應(yīng)的Realm進(jìn)行校驗,認(rèn)證成功則返回用戶屬性
AuthenticationInfo info = realm.doGetAuthenticationInfo(token);

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

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

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

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

Shiro認(rèn)證與授權(quán)原理是什么

可以看出,整個認(rèn)證過程中涉及到了SecurityManager、Subject、Authenticator、Realm等組件,相關(guān)組件的功能可參考架構(gòu)圖中的功能說明。

授權(quán)原理

實例中授權(quán)調(diào)用的代碼比較少,主要就是以下幾個方法:

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

下面以hasRole方法為例,進(jìn)行追蹤分析源代碼,看看具體的實現(xiàn)原理。

// 檢查是否有相應(yīng)角色權(quán)限
currentUser.hasRole("schwartz");

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

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

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

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

// AuthorizingRealm中,執(zhí)行Realm進(jìn)行授權(quán)操作
AuthorizationInfo info = this.doGetAuthorizationInfo(principals);

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

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

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

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

可以看出,整個認(rèn)證過程中涉及到了SecurityManager、Subject、Authorizer、Realm等組件,相關(guān)組件的功能可參考架構(gòu)圖中的功能說明。

自定義Realm

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

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

public class AuthenticationTest {

    SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();

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

    @Test
    public void testAuthentication() {
        // 1.構(gòu)建SecurityManager環(huán)境
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        defaultSecurityManager.setRealm(simpleAccountRealm);

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

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

實踐中自定義Realm的方法通常是繼承AuthorizingRealm類,并實現(xiàn)其doGetAuthorizationInfo方法和doGetAuthenticationInfo方法。在上面的流程梳理過程中,我們已經(jīng)知道doGetAuthorizationInfo方法為授權(quán)功能的實現(xiàn),而doGetAuthenticationInfo方法為認(rèn)證的功能實現(xiàn)。關(guān)于具體實例,后續(xù)會用專門的實例來講解。

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

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI