您好,登錄后才能下訂單哦!
本篇內容介紹了“Shiro認證與授權原理是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
Shiro作為常用的權限框架,可被用于解決認證、授權、加密、會話管理等場景。Shiro對其API進行了友好的封裝,如果單純的使用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的認證流程涉及到哪些組件。
認證的入口程序是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);
上述代碼包括了認證過程中一些核心流程,抽離出核心部分,整理成流程圖如下:
可以看出,整個認證過程中涉及到了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方法類似,讀者可自行追蹤),如下:
可以看出,整個認證過程中涉及到了SecurityManager、Subject、Authorizer、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認證與授權原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。