1. 程式人生 > >Shiro認證與授權原始碼分析

Shiro認證與授權原始碼分析

這裡使用官方提供的demo進行除錯,進入原始碼分析。

public class Quickstart {

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


    public static void main(String[] args) {
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        SecurityUtils.setSecurityManager(securityManager);

        // 獲取當前使用者:
Subject currentUser = SecurityUtils.getSubject(); // 獲取當前使用者的會話 (不依賴於Web容器!!!) 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 + "]"); } if (!currentUser.isAuthenticated()) { 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 (AuthenticationException ae) { //unexpected condition? error? } } //列印身份標識 (這裡的身份標識是使用者名稱): log.info("User [" + currentUser.getPrincipal() + "] logged in successfully."); //測試使用者角色: 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!"); } currentUser.logout(); System.exit(0); } }

身份認證過程

官方文件中提供了一張身份認證的圖,直接看這張圖可能還不能完全掌握認證的過程。除錯原始碼原始碼過後,再回頭看這張圖,這張圖才會深深的烙印在腦海中。

img

1. 從Demo中的Subject.login方法開始

登入

Subject介面實現類如下,這裡demo不是Web環境,所以使用的實現類是DelegatingSubject:

Subject實現類

DelegateSubject代理SecurityManager.login

2. SecurityManager.login()

SecurityManager介面的實現類

SecurityManager

DefaultSecurityManager

認證器

3. Authenticator認證器

認證器的實現類,SecurityMananger也繼承自Authenticator,通過檢視AuthenticatingSecurityManager原始碼其實就是Authenticator的代理。而真正實現認證功能的Authenticator實現類只有一個ModularRealmAuthenticator,從類的名字可以看出這個認證器的實現原理——模組化認證器:一個Realm就是一個認證模組。

Authenticator

認證器原始碼實現:

AbstractAuthenticator

模組化認證器:

模組化認證器

單模組認證

官方提供的這個例子就是單模組認證(IniRealm,使用者、角色、許可權等資訊儲存在ini配置檔案中)。

單模組認證很簡單,它直接呼叫realm.getAuthenticationInfo方法。

doSingleRealmAuthentication

多模組認證

doMultiRealmAuthentication

4. 多模組認證策略AuthenticationStrategy

Shiro提供了三種認證策略

認證策略

AuthenticationStrategy 描述
如果一個(或多個)Realm認證成功,才被認為是成功的。如果沒有任何一個驗證成功,則認證失敗。
只有從第一個成功驗證的領域返回的資訊將被使用,所有後面的Realm的認證資訊將被忽略。如果沒有任何一個驗證成功,則認證失敗。
所有配置的Realm都必須認證成功,才能被認為是成功的。如果有任何一個認證不成功,則認證失敗。

ModularRealmAuthenticator預設使用AtLeastOneSuccessfulStrategy

如果想修改認證策略可以在ini檔案中配置:

[main]
...
authcStrategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy

securityManager.authenticator.authenticationStrategy = $authcStrategy

...

5. 認證模組Realm的實現

Shiro提供瞭如下的認證模組實現類,在官方的這個Demo中,由於使用的是Ini配置檔案的方式,所以使用的Realm是IniRealm。

通常我們的使用者許可權資訊儲存在資料庫中,需要我們繼承AuthenticatingRealm,並重載它的doGetAuthenticationInfo方法來從資料庫中獲取使用者身份認證資訊。但是大多數情況我們會繼承AuthorizingRealm,因為它不僅僅包括認證,還包括授權過程,通過過載它的doGetAuthorizationInfo方法實現授權。

Realm模組實現類

授權過程

授權主要在呼叫Subject.hasRole或Subject.isPermitted等檢查角色或許可權的方法時觸發。

Subject檢查許可權的方法

官方也提供了一張圖來描述授權的過程:

img

授權過程與身份認證的過程程式碼很類似(其實更簡單),我就不在文章中具體貼圖了,感興趣的讀者可以自行除錯。