用戶權(quán)限模型在揭開 Shiro 面紗之前,,我們需要認知用戶權(quán)限模型。本文所提到用戶權(quán)限模型,,指的是用來表達用戶信息及用戶權(quán)限信息的數(shù)據(jù)模型,。即能證明“你是誰,?”、“你能訪問多少受保護資源,?”,。為實現(xiàn)一個較為靈活的用戶權(quán)限數(shù)據(jù)模型,通常把用戶信息單獨用一個實體表示,,用戶權(quán)限信息用兩個實體表示,。
圖 1. 用戶權(quán)限模型認證與授權(quán)Shiro 認證與授權(quán)處理過程
Shiro Realm在 Shiro 認證與授權(quán)處理過程中,,提及到 Realm。Realm 可以理解為讀取用戶信息,、角色及權(quán)限的 DAO,。由于大多 Web 應(yīng)用程序使用了關(guān)系數(shù)據(jù)庫,因此實現(xiàn) JDBC Realm 是常用的做法,,后面會提到 CAS Realm,,另一個 Realm 的實現(xiàn)。 清單 1. 實現(xiàn)自己的 JDBC Realmpublic class MyShiroRealm extends AuthorizingRealm{ // 用于獲取用戶信息及用戶權(quán)限信息的業(yè)務(wù)接口 private BusinessManager businessManager; // 獲取授權(quán)信息 protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { String username = (String) principals.fromRealm( getName()).iterator().next(); if( username != null ){ // 查詢用戶授權(quán)信息 Collection<String> pers=businessManager.queryPermissions(username); if( pers != null && !pers.isEmpty() ){ SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); for( String each:pers ) info.addStringPermissions( each ); return info; } } return null; } // 獲取認證信息 protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authcToken ) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authcToken; // 通過表單接收的用戶名 String username = token.getUsername(); if( username != null && !"".equals(username) ){ LoginAccount account = businessManager.get( username ); if( account != null ){ return new SimpleAuthenticationInfo( account.getLoginName(),account.getPassword(),getName() ); } } return null; } } 代碼說明:
為何對 Shiro 情有獨鐘或許有人要問,我一直在使用 Spring,,應(yīng)用程序的安全組件早已選擇了 Spring Security,,為什么還需要 Shiro ?當然,,不可否認 Spring Security 也是一款優(yōu)秀的安全控制組件,。本文的初衷不是讓您必須選擇 Shiro 以及必須放棄 Spring Security,秉承客觀的態(tài)度,,下面對兩者略微比較:
與 Spring 集成在 Java Web Application 開發(fā)中,,Spring 得到了廣泛使用;與 EJB 相比較,,可以說 Spring 是主流,。Shiro 自身提供了與 Spring 的良好支持,在應(yīng)用程序中集成 Spring 十分容易,。 有了前面提到的用戶權(quán)限數(shù)據(jù)模型,并且實現(xiàn)了自己的 Realm,,我們就可以開始集成 Shiro 為應(yīng)用程序服務(wù)了,。 Shiro 的安裝Shiro 的安裝非常簡單,在 Shiro 官網(wǎng)下載 shiro-all-1.2.0.jar,、shiro-cas-1.2.0.jar(單點登錄需要),,及 SLF4J 官網(wǎng)下載 Shiro 依賴的日志組件 slf4j-api-1.6.1.jar。Spring 相關(guān)的 JAR 包這里不作列舉,。這些 JAR 包需要放置到 Web 工程 /WEB-INF/lib/ 目錄。至此,,剩下的就是配置了,。 配置過濾器首先,配置過濾器讓請求資源經(jīng)過 Shiro 的過濾處理,,這與其它過濾器的使用類似,。 清單 2. web.xml 配置<filter> <filter-name>shiroFilter</filter-name> <filter-class> org.springframework.web.filter.DelegatingFilterProxy </filter-class> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> Spring 配置接下來僅僅配置一系列由 Spring 容器管理的 Bean,,集成大功告成。各個 Bean 的功能見代碼說明,。 清單 3. Spring 配置<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.do"/> <property name="successUrl" value="/welcome.do"/> <property name="unauthorizedUrl" value="/403.do"/> <property name="filters"> <util:map> <entry key="authc" value-ref="formAuthenticationFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <value> /=anon /login.do*=authc /logout.do*=anon # 權(quán)限配置示例 /security/account/view.do=authc,perms[SECURITY_ACCOUNT_VIEW] /** = authc </value> </property> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myShiroRealm"/> </bean> <bean id="myShiroRealm" class="xxx.packagename.MyShiroRealm"> <!-- businessManager 用來實現(xiàn)用戶名密碼的查詢 --> <property name="businessManager" ref="businessManager"/> <property name="cacheManager" ref="shiroCacheManager"/> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManager" ref="cacheManager"/> </bean> <bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"/> 代碼說明:
實現(xiàn)驗證碼認證驗證碼是有效防止暴力破解的一種手段,,常用做法是在服務(wù)端產(chǎn)生一串隨機字符串與當前用戶會話關(guān)聯(lián)(我們通常說的放入 Session),,然后向終端用戶展現(xiàn)一張經(jīng)過“擾亂”的圖片,只有當用戶輸入的內(nèi)容與服務(wù)端產(chǎn)生的內(nèi)容相同時才允許進行下一步操作,。 產(chǎn)生驗證碼作為演示,,我們選擇開源的驗證碼組件 kaptcha。這樣,,我們只需要簡單配置一個 Servlet,,頁面通過 IMG 標簽就可以展現(xiàn)圖形驗證碼。 清單 4. web.xml 配置<!-- captcha servlet--> <servlet> <servlet-name>kaptcha</servlet-name> <servlet-class> com.google.code.kaptcha.servlet.KaptchaServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>kaptcha</servlet-name> <url-pattern>/images/kaptcha.jpg</url-pattern> </servlet-mapping> 擴展 UsernamePasswordTokenShiro 表單認證,,頁面提交的用戶名密碼等信息,,用 UsernamePasswordToken 類來接收,很容易想到,,要接收頁面驗證碼的輸入,,我們需要擴展此類: 清單 5. CaptchaUsernamePasswordTokenpublic class CaptchaUsernamePasswordToken extends UsernamePasswordToken{ private String captcha; // 省略 getter 和 setter 方法 public CaptchaUsernamePasswordToken(String username, char[] password, boolean rememberMe, String host,String captcha) { super(username, password, rememberMe, host); this.captcha = captcha; } } 擴展 FormAuthenticationFilter接下來我們擴展 FormAuthenticationFilter 類,首先覆蓋 createToken 方法,,以便獲取 CaptchaUsernamePasswordToken 實例,;然后增加驗證碼校驗方法 doCaptchaValidate,;最后覆蓋 Shiro 的認證方法 executeLogin,在原表單認證邏輯處理之前進行驗證碼校驗,。 清單 6. CaptchaUsernamePasswordTokenpublic class CaptchaFormAuthenticationFilter extends FormAuthenticationFilter{ public static final String DEFAULT_CAPTCHA_PARAM = "captcha"; private String captchaParam = DEFAULT_CAPTCHA_PARAM; public String getCaptchaParam() { return captchaParam; } public void setCaptchaParam(String captchaParam) { this.captchaParam = captchaParam; } protected String getCaptcha(ServletRequest request) { return WebUtils.getCleanParam(request, getCaptchaParam()); } // 創(chuàng)建 Token protected CaptchaUsernamePasswordToken createToken( ServletRequest request, ServletResponse response) { String username = getUsername(request); String password = getPassword(request); String captcha = getCaptcha(request); boolean rememberMe = isRememberMe(request); String host = getHost(request); return new CaptchaUsernamePasswordToken( username, password, rememberMe, host,captcha); } // 驗證碼校驗 protected void doCaptchaValidate( HttpServletRequest request ,CaptchaUsernamePasswordToken token ){ String captcha = (String)request.getSession().getAttribute( com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY); if( captcha!=null && !captcha.equalsIgnoreCase(token.getCaptcha()) ){ throw new IncorrectCaptchaException ("驗證碼錯誤,!"); } } // 認證 protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { CaptchaUsernamePasswordToken token = createToken(request, response); try { doCaptchaValidate( (HttpServletRequest)request,token ); Subject subject = getSubject(request, response); subject.login(token); return onLoginSuccess(token, subject, request, response); } catch (AuthenticationException e) { return onLoginFailure(token, e, request, response); } } } 代碼說明:
添加 IncorrectCaptchaException前面驗證碼校驗不通過,,我們拋出一個異常 IncorrectCaptchaException,,此類繼承 AuthenticationException,之所以需要擴展一個新的異常類,,為的是在頁面能更精準顯示錯誤提示信息,。 清單 7. IncorrectCaptchaExceptionpublic class IncorrectCaptchaException extends AuthenticationException{ public IncorrectCaptchaException() { super(); } public IncorrectCaptchaException(String message, Throwable cause) { super(message, cause); } public IncorrectCaptchaException(String message) { super(message); } public IncorrectCaptchaException(Throwable cause) { super(cause); } } 頁面展現(xiàn)驗證碼錯誤提示信息清單 8. 頁面認證錯誤信息展示Object obj=request.getAttribute( org.apache.shiro.web.filter.authc.FormAuthenticationFilter .DEFAULT_ERROR_KEY_ATTRIBUTE_NAME); AuthenticationException authExp = (AuthenticationException)obj; if( authExp != null ){ String expMsg=""; if(authExp instanceof UnknownAccountException || authExp instanceof IncorrectCredentialsException){ expMsg="錯誤的用戶賬號或密碼!"; }else if( authExp instanceof IncorrectCaptchaException){ expMsg="驗證碼錯誤,!"; }else{ expMsg="登錄異常 :"+authExp.getMessage() ; } out.print("<div class=\"error\">"+expMsg+"</div>"); } 實現(xiàn)單點登錄前面章節(jié),,我們認識了 Shiro 的認證與授權(quán),并結(jié)合 Spring 作了集成實現(xiàn)?,F(xiàn)實中,,有這樣一個場景,我們擁有很多業(yè)務(wù)系統(tǒng),,按照前面的思路,,如果訪問每個業(yè)務(wù)系統(tǒng),都要進行認證,,這樣是否有點難讓人授受,。有沒有一種機制,讓我們只認證一次,,就可以任意訪問目標系統(tǒng)呢,? 上面的場景,就是我們常提到的單點登錄 SSO,。Shiro 從 1.2 版本開始對 CAS 進行支持,,CAS 就是單點登錄的一種實現(xiàn)。 Shiro CAS 認證流程
CAS RealmShiro 提供了一個名為 CasRealm 的類,與前面提到的 JDBC Realm 相似,,該類同樣包括認證和授權(quán)兩部分功能,。認證就是校驗從 CAS 服務(wù)端返回的 ticket 是否有效;授權(quán)還是獲取用戶權(quán)限信息,。 實現(xiàn)單點登錄功能,,需要擴展 CasRealm 類。 清單 9. Shiro CAS Realmpublic class MyCasRealm extends CasRealm{ // 獲取授權(quán)信息 protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { //... 與前面 MyShiroRealm 相同 } public String getCasServerUrlPrefix() { return "http://casserver/login"; } public String getCasService() { return "http://casclient/shiro-cas"; } 16 } 代碼說明:
CAS Spring 配置實現(xiàn)單點登錄的 Spring 配置與前面類似,,不同之處參見代碼說明。 清單 10. Shiro CAS Spring 配置<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="http://casserver/login?service=http://casclient/shiro-cas"/> <property name="successUrl" value="/welcome.do"/> <property name="unauthorizedUrl" value="/403.do"/> <property name="filters"> <util:map> <entry key="authc" value-ref="formAuthenticationFilter"/> <entry key="cas" value-ref="casFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <value> /shiro-cas*=cas /logout.do*=anon /casticketerror.do*=anon # 權(quán)限配置示例 /security/account/view.do=authc,perms[SECURITY_ACCOUNT_VIEW] /** = authc </value> </property> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myShiroRealm"/> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- CAS Realm --> <bean id="myShiroRealm" class="xxx.packagename.MyCasRealm"> <property name="cacheManager" ref="shiroCacheManager"/> </bean> <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManager" ref="cacheManager"/> </bean> <bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"/> <!-- CAS Filter --> <bean id="casFilter" class="org.apache.shiro.cas.CasFilter"> <property name="failureUrl" value="casticketerror.do"/> </bean> 代碼說明:
總結(jié)至此,我們對 Shiro 有了較為深入的認識,。Shiro 靈活,,功能強大,幾乎能滿足我們實際應(yīng)用中的各種情況,,還等什么呢,?讓我開始使用 Shiro 為應(yīng)用程序護航吧! |
|