第一個Apache Shiro程序
如果你是初次接觸Apache Shiro,該文章將指導(dǎo)你創(chuàng)建一個初級的非常簡單的使用Apache Shiro進行安全認證的程序,同時我們將討論Shiro的核心理念以幫助你熟悉Shiro的設(shè)計方式和API。
如果你確實不想按照該示例一步一步地編寫代碼,你可以從下面地址下載一個基本上完全相同的程序作為參考,選擇下載位置: 在Apache Shiro的版本庫中: https://svn./repos/asf/shiro/trunk/samples/quickstart/ 在Apache Shiro的源碼發(fā)布的samples/quickstart目錄中,源碼發(fā)布在 Download(http://shiro./download.html)頁面。
Setup 在這個簡單示例中,我們將建立一個非常簡單的命令行程序,你可以從中感受一下Shiro的API。 注意:任何程序 Apache Shrio從設(shè)計之初就是為了支持所有程序--從最小的命令行程序到大型的集群的web程序,雖然我們在這個向?qū)е兄皇褂昧艘粋€簡單的程序,但要知道無論你的程序如何創(chuàng)建發(fā)布到何處,這種方式都適用。
該示例需要Java1.5及更高版本,同時用Apache Maven作為建造工具,但這不是Apache Shiro所必須的。你可以獲得Shiro的jar并以你喜歡的任何方式將其加入你的程序中,例如你可以使用ant和ivy。 在該示例中,請確定你使用的是Maven2.2.1或更高版本,你可以在命令窗口中執(zhí)行“mvn --version”看到和下面類似的輸出:
Testing Maven Installation hazlewood:~/shiro-tutorial$ mvn --version Apache Maven 2.2.1 (r801777; 2009-08-06 12:16:01-0700) Java version: 1.6.0_24 Java home: /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home Default locale: en_US, platform encoding: MacRoman OS name: "mac os x" version: "10.6.7" arch: "x86_64" Family: "mac"
現(xiàn)在,創(chuàng)建一個新的目錄,例如shiro-tutorial 并將下面的Maven的pom.xml文件保存在同一目錄下: pom.xml
示例類文件 我們準(zhǔn)備運行的是一個命令行程序,所以我們需要創(chuàng)建一個帶有public static void main(String[] args)函數(shù)的Java類。 在pom.xml同目錄里,創(chuàng)建一個src/main/java子目錄,在該子目錄里創(chuàng)建一個Tutorial.java文件,內(nèi)容如下: src/main/java/Tutorial.java
先不要考慮里面的import,我們馬上就會獲取它們,現(xiàn)在,我們已經(jīng)有了一個典型的命令行程序'shell',這個程序所做的事情就是輸出文字“My First Apache Shiro Application”然后退出。
運行 在你的該示例項目根目錄里(例如shiro-tutorial)執(zhí)行下面的命令行來運行程序: mvn compile exec:java 你可以看到我們的這個小程序運行并且退出,你將看到類似于下面這樣的輸出住處(注意加粗字體,標(biāo)示著我們的輸出) Run the Application lhazlewood:~/projects/shiro-tutorial$ mvn compile exec:java
... a bunch of Maven output ...
1 [Tutorial.main()] INFO Tutorial - My First Apache Shiro Application lhazlewood:~/projects/shiro-tutorial\$
我們驗證程序已經(jīng)成功運行--現(xiàn)在,讓我們加上Apache Shiro,今后,你可以在我們加上任何代碼之后運行mvn compile exec:java命令查看我們更改的結(jié)果。
使用Shiro 使用shiro要理解的第一件事情是shiro幾乎所有的事情都和一個中心組件SecurityManager有關(guān),對于那些熟悉Java security的人請注意:這和java.lang.SecurityManager不是一回事。
我們將在Architecture章節(jié)詳細描述shiro的設(shè)計,但現(xiàn)在有必要知道Shrio SecurityManager是程序中Shiro的核心,每一個程序都必定會存在一個SecurityManager,所以,在我們這個示例程序中必須做的第一件事情是建立一個SecurityManager實例。
配置 雖然我們可以直接對SecurityManager實例化,但在Java代碼中對Shiro的SecurityManager所須的選項和內(nèi)部組件進行配置會讓人感覺有點小痛苦--而將這些SecurityManager配置用一個靈活的配置文件實現(xiàn)就會簡單地多。 為此,Shiro默認提供了一個基本的INI配置文件的解決方案,人們已經(jīng)對龐大的XML文件有些厭倦了,而一個INI文件易讀易用,而且所依賴的組件很少,稍后你就會通過一個簡單易懂的示例明白INI在對簡單對象進行配置的時候是非常有效率的,比如SecurityManager。
多種配置選擇 Shiro的SecurityManager的實現(xiàn)和其所依賴的組件都是JavaBean,所以可以用多種形式對Shiro進行配置,比如XML(Spring, JBoss, Guice, 等等),YAML, JSON, Groovy Builder markup,及其它,INI只是Shiro一種最基本的配置方式,使得其可以在任何環(huán)境中進行配置比如在那些沒有以上配置形式的環(huán)境中。
shiro.ini 在這個示例中我們使用一個INI文件來配置Shiro SecurityManager,首先,在pom.xml同目錄中創(chuàng)建一個src/main/resources子目錄,在該子目錄中創(chuàng)建一個shiro.ini文件,內(nèi)容如下: src/main/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
可以看到,在該配置文件中最基礎(chǔ)地配置了幾個靜態(tài)的帳戶,對我們這一個程序已經(jīng)足夠了,在以后的章節(jié)中,將會看到如何使用更復(fù)雜的用戶數(shù)據(jù)比如數(shù)據(jù)庫、LDAP和活動目錄等。
引用配置文件 現(xiàn)在我們已經(jīng)定義了一個INI文件,我們可以在我們的示例程序中創(chuàng)建SecurityManager實例了,將main函數(shù)中的代碼進行如下調(diào)整: public static void main(String[] args) {
log.info("My First Apache Shiro Application");
//1. Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2. SecurityManager securityManager = factory.getInstance();
//3. SecurityUtils.setSecurityManager(securityManager);
System.exit(0); }
這就是我們要做的--僅僅使用三行代碼就把Shiro加進了我們的程序,就是這么簡單。
執(zhí)行mvn compile exec:java 可以看到程序成功的運行(由于Shiro默認在debug或更底層才記錄日志,所以你不會看到任何Shiro的日志住處--只要運行時沒有錯誤提示,你就可以知道已經(jīng)成功了)。
上面所加入的代碼做了下面的事情: 1. 使用Shiro的IniSecurityManagerFactory加載了我們的shiro.ini文件,該文件存在于classpath根目錄里。這個執(zhí)行動作反映出shiro支持Factory Method Design Pattern。classpath:資源的指示前綴,告訴shiro從哪里加載ini文件(其它前綴,如url:和file:也被支持)。 2.factory.getInstance()方法被調(diào)用,該方法分析INI文件并根據(jù)配置文件返回一個SecurityManager實例。 3.在這個簡單示例中,我們將SecurityManager設(shè)置成了static (memory) singleton,可以通過JVM訪問,注意如果你在一個JVM中加載多個使用shiro的程序時不要這樣做,在這個簡單示例中,這是可以的,但在其它成熟的應(yīng)用環(huán)境中,通常會將SecurityManager放在程序指定的memory(如在web中的ServletContexct或者Spring、Guice、 JBoss DI 容器實例)中。
使用Shiro 現(xiàn)在我們的SecurityManager已經(jīng)準(zhǔn)備好了,我們可以開始進行我們真正關(guān)心的事情--執(zhí)行安全操作了。
為了保護我們的程序安全,我們或許問自己最多的問題就是“誰是當(dāng)前的用戶?”或者“當(dāng)前用戶是否允許做某件事?”通常我們會在寫代碼或者設(shè)計用戶接口的時候問這些問題:程序通常建立在用戶基礎(chǔ)上,程序功能展示(和安全)也基于每一個用戶。所以,通常我們考慮我們程序安全的方法也建立在當(dāng)前用戶的基礎(chǔ)上,Shiro的API提供了'the current user'概念,即它的Subject。 在幾乎所有的環(huán)境中,你可以通過如下語句得到當(dāng)前用戶的信息: Subject currentUser = SecurityUtils.getSubject();
使用SecurityUtils.getSubject(),我們可以獲取當(dāng)前執(zhí)行的Subject,Subject是一個安全術(shù)語意思是“當(dāng)前運行用戶的指定安全視圖(a security-specific view of the currently executing user)”,這里并不稱之為“User”因為“User”這個詞通常和一個人相關(guān),但在安全認證中,“Subject”可以認為是一個人,也可以認為是第三方進程、時鐘守護任務(wù)、守護進程帳戶或者其它。它可簡單描述為“當(dāng)前和軟件進行交互的事件”,在大多數(shù)情況下,你可以認為它是一個“人(User)”。
在一個獨立的程序中調(diào)用getSubject()會在程序指定位置返回一個基于用戶數(shù)據(jù)的Subject,在服務(wù)器環(huán)境(如web程序)中,它將獲取一個和當(dāng)前線程或請求相關(guān)的基于用戶數(shù)據(jù)的Subject。 現(xiàn)在你得到了Subject,你可以利用它做什么呢? 如果你針對該用戶希望一些事情在程序當(dāng)前會話期內(nèi)可行,你可以獲取他們的session: Session session = currentUser.getSession(); session.setAttribute( "someKey", "aValue" );
Session是shiro指定的一個實例,提供基本上所有HttpSession的功能,但具備額外的好處和不同:它不需要一個HTTP環(huán)境!
如果發(fā)布到一個web程序中,默認情況下Session將會使用HttpSession作為基礎(chǔ),但是,在一個非web程序中,比如該簡單示例程序中,Shiro將自動默認使用它的Enterprise Session Management,這意味著你可以在任何程序中使用相同的API,而根本不需要考慮發(fā)布環(huán)境!這打開了一個全新的世界,從此任何需要session的程序不再需要強制使用HttpSession或者EJB Stateful Session,并且,終端可以共享session數(shù)據(jù)。
現(xiàn)在你可以獲取一個Subject和它們的Session,真正填充有用的代碼如檢測其是否被允許做某些事情如何?比如檢查其角色和權(quán)限?
我們只能對一個已知用戶做這些檢測,如上我們獲取Subject實例表示當(dāng)前用戶,但是當(dāng)前用戶是認證,嗯,他們是任何人--直到他們至少登錄一次,我們現(xiàn)在就做這件事情: 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. 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? }
這里有許多不同類別的異常你可以檢測到,也可以拋出你自己異常。
小貼士: 最好的方式是將普通的失敗信息反饋給用戶,你總不會希望幫助黑客來攻擊你的系統(tǒng)吧。
好,到現(xiàn)在為止,我們有了一個登錄用戶,接下來我們還可以做什么?
讓我們顯示他們是誰: //print their identifying principal (in this case, a username): log.info( "User [" + currentUser.getPrincipal() + "] logged in successfully." );
我們也可以判斷他們是否擁有某個特定動作或入口的權(quán)限: 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."); }
同樣,我們還可以執(zhí)行非常強大的實例級別的權(quán)限檢測,檢測用戶是否具備訪問某個類型特定實例的權(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!"); }
非常容易的事,對嗎?
最后,當(dāng)用記不再使用系統(tǒng),可以退出登錄: currentUser.logout(); //removes all identifying information and invalidates their session too.
最終代碼 在加入上述代碼后,下面的就是我們完整的文件,你可以自由編輯和運行它,可以嘗試改變安全檢測(以及INI配置): Final src/main/java/Tutorial.java
總結(jié) 非常希望這示例介紹能幫助你理解如何在基礎(chǔ)程序中加入Shiro,并理解Shiro的設(shè)計理念,Subject和SecurityManager。 但這個程序太簡單了,你可能會問自己,“如果我不想使用INI用戶賬號,而希望連接更為復(fù)雜的用戶數(shù)據(jù)源呢?” 解決這些問題需要更深入地了解shiro的架構(gòu)和配置機制,我們將在下一節(jié)Architecture中介紹。
|
|
來自: 且看且珍惜 > 《apache-shiro》