理解Apache Shiro中的主題
毫無疑問,Apache Shiro中最重要的概念是主題。
“主題”只是一個安全術語,它指的是應用程序用戶特定于安全的“視圖”。Shiro主題實例代表了單個應用程序用戶的安全狀態和操作。
這些操作包括:
-
身份驗證(登錄)
-
授權(訪問控制)
-
會話的訪問
-
注銷
我們最初想叫它“用戶”,因為這“很有意義”,但我們決定不叫它:太多的應用程序已經有了它們自己的用戶類/框架的api,我們不想與它們沖突。
此外,在安全領域,術語“主體”實際上是公認的命名法。
Shiro的API鼓勵應用程序采用以主題為中心的編程范式。
在編寫應用程序邏輯時,大多數應用程序開發人員都想知道當前執行的用戶是誰。
雖然應用程序通常可以通過自己的機制(UserService,等等)查找任何用戶,但當涉及到安全性時,最重要的問題是“當前用戶是誰?”
雖然任何主題都可以通過使用SecurityManager獲得,但僅基于當前用戶/主題的應用程序代碼更加自然和直觀。
當前正在執行的主體
在幾乎所有的環境中,你都可以通過使用 org.apache.shiro.SecurityUtils
來獲取當前正在執行的主題:
Subject currentUser = SecurityUtils.getSubject();
在獨立的應用程序中,getSubject()調用可能會根據應用程序特定位置的用戶數據返回一個主題,而在服務器環境(例如web應用程序)中,它會根據與當前線程或傳入請求相關聯的用戶數據獲取主題。
在你學習了現在的課程之后,你能做些什么呢?
如果你想讓用戶在他們當前的會話中使用這些東西,你可以得到他們的會話:
Session session = currentUser.getSession();
session.setAttribute( "someKey", "aValue" );
Session是一個特定于shiro的實例,它提供了常規httpsession所使用的大部分功能,但也有一些額外的好處和一個很大的區別:它不需要HTTP環境!
如果在web應用程序中部署,默認情況下會話將是基于HttpSession的。但是,
在一個非web環境中,比如這個簡單的快速入門,Shiro默認情況下會自動使用它的企業會話管理。這意味著無論部署環境如何,您都可以在應用程序的任何層中使用相同的API。這打開了一個全新的應用程序世界,因為任何需要會話的應用程序都不需要強制使用HttpSession或EJB有狀態會話bean。而且,任何客戶機技術現在都可以共享會話數據。
所以現在你可以獲得一個對象和他們的會話。那么真正有用的東西呢,比如檢查它們是否被允許做一些事情,比如檢查角色和權限?
登陸驗證
我們只能對已知的用戶做這些檢查。上面的Subject實例表示當前用戶,但是誰是當前用戶呢?嗯,他們是匿名的——也就是說,直到他們至少登錄一次。
那么,讓我們這樣做:
if ( !currentUser.isAuthenticated() ) {//collect user principals and credentials in a gui specific manner//such as username/password html form, X509 certificate, OpenID, etc.//We'll use the username/password example here since it is the most common.//(do you know what movie this is from? ;)UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");//this is all you have to do to support 'remember me' (no config - built in!):token.setRememberMe(true);currentUser.login(token);
}
就是這樣!再簡單不過了。
但是,如果他們的登錄嘗試失敗怎么辦?你可以捕捉各種特定的異常,告訴你到底發生了什么:
try {currentUser.login( token );//if no exception, that's it, we're done!
} catch ( UnknownAccountException uae ) {//username wasn't in the system, show them an error message?
} catch ( IncorrectCredentialsException ice ) {//password didn't match, try again?
} catch ( LockedAccountException lae ) {//account for that username is locked - can't login. Show them a message?
}... more types exceptions to check if you want ...
} catch ( AuthenticationException ae ) {//unexpected condition - error?
}
作為應用程序/GUI開發人員,您可以選擇是否根據異常顯示最終用戶消息(例如,“系統中沒有具有該用戶名的帳戶”)。
有許多不同類型的異常你可以檢查,或者拋出你自己的Shiro可能無法解釋的自定義條件。
更多信息請參閱AuthenticationException JavaDoc。
現在,我們有了一個登錄用戶。
權限校驗
我們還能做什么?
讓我們說說他們是誰:
//print their identifying principal (in this case, a username):
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:weild" ) ) {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(); //removes all identifying information and invalidates their session too.