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);
}
}
身份認證過程
官方文件中提供了一張身份認證的圖,直接看這張圖可能還不能完全掌握認證的過程。除錯原始碼原始碼過後,再回頭看這張圖,這張圖才會深深的烙印在腦海中。
1. 從Demo中的Subject.login方法開始
Subject介面實現類如下,這裡demo不是Web環境,所以使用的實現類是DelegatingSubject:
2. SecurityManager.login()
SecurityManager介面的實現類
3. Authenticator認證器
認證器的實現類,SecurityMananger也繼承自Authenticator,通過檢視AuthenticatingSecurityManager原始碼其實就是Authenticator的代理。而真正實現認證功能的Authenticator實現類只有一個ModularRealmAuthenticator,從類的名字可以看出這個認證器的實現原理——模組化認證器:一個Realm就是一個認證模組。
認證器原始碼實現:
模組化認證器:
單模組認證
官方提供的這個例子就是單模組認證(IniRealm,使用者、角色、許可權等資訊儲存在ini配置檔案中)。
單模組認證很簡單,它直接呼叫realm.getAuthenticationInfo方法。
多模組認證
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
方法實現授權。
授權過程
授權主要在呼叫Subject.hasRole或Subject.isPermitted等檢查角色或許可權的方法時觸發。
官方也提供了一張圖來描述授權的過程:
授權過程與身份認證的過程程式碼很類似(其實更簡單),我就不在文章中具體貼圖了,感興趣的讀者可以自行除錯。