原文:https://michaelzx.github.io/2016/jfinal-shiro-integration/html
英文好的能夠直接看官網教程,英文很差的能夠看下開濤的博客《跟我學Shiro》系列java
在看教程以前,最好了解想一些shiro的概念:Apache Shiro Terminologygit
一開始的時候,我本身搞了一個JFinal的全局Interceptor
,用來作Shiro
的權限判斷。緣由是一開始不瞭解Shiro,不知道運用Shiro
的Filter
,理所固然地從JFinal
的層面進行思考。github
固然,在JFinal
的Interceptor
中也是能夠作一些權限的判斷的,不過順序上面先要通過Shiro
的Filter
,而後纔會到JFinal
的Filter
,真正進入JFinal
。web
能夠直接拿來用,能夠作參考,我在實踐的時候,也參考了很多ajax
項目地址:http://git.oschina.net/myaniu/jfinalshiroplugin數據庫
項目地址:http://git.oschina.net/jayqqaa12/JFinal_Authorityexpress
項目地址:https://github.com/Dreampie/jfinal-shiroapache
部分現成方案中使用了Plugin、Interceptor、Annotation的方式來集成Shrio,我的不是很是喜歡,我的感受有2個痛點:緩存
一、須要好多代碼去實現Plugin、Interceptor、Annotation,雖然別人寫好了,可是你仍是得去了解源碼,萬一有個坑呢:),畢竟不是什麼大而成熟且運用普遍的解決方案
二、你項目裏有這麼多代碼,想一想一下
你得去找,去改,從新編譯,從新部署。無論你瘋不瘋,反正我感受我會瘋的。
既然Shiro是專業的那就讓它作好該作的。
自定義Realm
+自定義filters
+url表達式
,來實現自定義的驗證方式我如今使用是
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.4</version> </dependency>
<listener> <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> </listener> <filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> </filter> <filter-mapping> <filter-name>ShiroFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping>
<listener> 節點定義了一個ServletContextListener,在web應用程序啓動的生時候啓動Shiro環境(包括shiro的SecurityManager)默認狀況下, 這個listener會自動去找咱們的WEB-INF/classes/shiro.ini。
<filter> 節點定義了主要的ShiroFilter.這個filter被要求去過濾全部進入web應用程序的請求,所以shiro能夠在一個請求到達應用程序以前進行必要的身份驗證和訪問控制。
<filter-mapping> 節點確保全部請求類型經過被ShiroFilterare提出(filed)filter-mapping節點通常是不指定dispatcher元素的,可是shiro須要它們都被定義,以便它可以過濾全部可能被web應用執行的不一樣請求類型。
如下是我經常使用的shiro.ini
[main] #sessionId相關設定 sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie sessionIdCookie.name=jshop-admin-web sessionIdCookie.path=/ sessionIdCookie.maxAge=1800 sessionIdCookie.httpOnly=true #sessionManager sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager sessionManager.sessionIdCookie = $sessionIdCookie sessionManager.sessionIdCookieEnabled = true sessionManager.globalSessionTimeout = 3600000 #Realm dbRealm = com.yourdomain.module.shiro.Realm #Cache cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager #--------------- securityManager.sessionManager=$sessionManager securityManager.realm = $dbRealm securityManager.cacheManager = $cacheManager #[filters] 這個已經被取消,併入main中 #自定義驗證過濾器 app_auth= com.yourdomain.module.shiro.AuthorizeFilter #登陸頁面 app_auth.loginUrl = /auth/login #未受權頁面 app_auth.unauthorizedUrl=/auth/unauthorized [urls] /test/** = anon /public/** = anon /uploads/** = anon /passport/* = anon /** = app_auth
這倆個彷佛沒啥好說的。
它定義了一個新的緩存管理實例. 緩存在Shiro的構架體系中是一個很是重要的部分 - 它減小了和數據存貯之間持續往返的通信。這個例子是使用了在單個JVM上比較好使的MemoryConstrainedCacheManager。若是對你的應用是部署在多個服務器(好比服務器集羣)的話,你將會想使用一個集羣緩存管理器的實現來替代。
它是很關鍵的一個地方,這是是須要本身實現的。它做爲shiro的一個組件,可讓shiro訪問到你的系統中的用戶、角色、權限等數據。
這是很是重要的一個節點,來配置哪些路徑映射哪些過濾器來進行鑑權,能夠用逗號分開,配置多個過濾器。
這裏的anon是shiro內置的一個過濾器,表示不須要進行鑑權。固然仍是不少的shiro內置鑑權過濾器.在後面簡單介紹下
1.2之後,filters被併入[main]節點,若是繼續保留也沒事兒,只是會出個警告而已。app_auth是我本身實現的一個filter系統中主要使用這個filter進行鑑權。
com.yourdomain.module.shiro.Realm
public class Realm extends AuthorizingRealm
主要是在登陸的時候,進行用戶身份驗證
/** * 獲取用戶驗證信息 * @param authcToken 所需驗證的token * @return null or 身份信息 * @throws AuthenticationException 驗證異常 */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authcToken; SysAdmin admin = AdminService.getByUsername(token.getUsername()); if (admin != null) { if(!admin.getPassword().equals(String.valueOf(token.getPassword()))){ throw new AuthenticationException("密碼錯誤"); } Db.update("update sys_admin set loginTime=?,loginCount=loginCount+1 where id=?",new Date(),admin.getId()); return new SimpleAuthenticationInfo(admin, admin.getPassword(),admin.getUsername()); } else { throw new AuthenticationException("用戶不存在"); } }
在第一次鑑權的時候進行調用,獲取並保存到chache中(沒有配置cache是否是每次都得調用?)
/** * 獲取用戶受權信息 * @param principals 用戶身份 * @return null or 受權信息 */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SysAdmin userInPrincipal = (SysAdmin) principals.getPrimaryPrincipal(); //根據用戶獲取權限 List<String> stringPermissions = AdminService.getPermissions(userInPrincipal.getId()); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //stringPermissions結構: //user //user:list //user:add //user:edit //... info.addStringPermissions(stringPermissions); return info; }
我這裏返回的:字符串權限表達式(字符串通配符權限),對於各自所對應的資源(主要就是url路徑),我是保存在數據庫中,方便進行配置,而後再加上緩存。在自定義filter中將url轉換成對應的表達式,而後進行鑑權。
public void login() { if(Boolean.FALSE.equals(validateCaptcha("captcha"))){ renderJson(CommonService.ajaxError("驗證碼錯誤")); return; } String username = getPara("username"); String password = HashKit.md5(getPara("password")); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); renderJson(CommonService.ajaxSuccess()); } catch (AuthenticationException e) { //雖然在realm中有具體的錯誤信息,可是安全起見,統一返回登陸失敗 renderJson(CommonService.ajaxError("登錄失敗")); } }
public void logout() { Subject subject = SecurityUtils.getSubject(); subject.logout(); redirect("/passport/login"); }
是否是感受很easy:)
com.yourdomain.module.shiro.AuthorizeFilter
public class AuthorizeFilter extends AuthorizationFilter
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { //-----------------用戶驗證------------------ Subject currentUser = getSubject(request, response); if (!currentUser.isAuthenticated()) return false; //-----------------獲取資源權限表達式------------- SysAdmin user = (SysAdmin) currentUser.getPrincipal(); //request中加入attribute便於controller調用admin的信息 request.setAttribute("admin",user); // 根據actionKey分析出權限表達式 HttpServletRequest hsr = ((HttpServletRequest) request); String root = hsr.getContextPath(); String URI = hsr.getRequestURI(); String actionKey = URI.replace(root,""); if("".equals(actionKey)) actionKey="/"; RoleService roleService = new RoleService(); String expression = roleService.getActionKeyExpression(actionKey); //-----------------進行鑑權------------- if (user==null) return false; else if(user.getStr("username").equals("superadmin")){ //超級管理員具備全部權限 return true; }else if(expression==null){ return false; }else if(currentUser.isPermitted(expression)){ //鑑權 return true; }else{ return false; } }
你能夠按照你本身的方式來構建一套,我相信對於大多數人應該不成問題,由於這個已經不屬於shiro的範疇了。本身搞幾個表,搞幾個配置界面,作下緩存策略等等。
有時候你的應用也許須要在界面上進行鑑權,好比按鈕啥的,這時候就可能須要擴展模板引擎的函數或者標籤。
能夠參考下 《beetl 和 shrio 結合》 http://my.oschina.net/xiandafu/blog/143109
只要解決如何擴展模板引擎的函數或者標籤,其餘,我想應該都是雷同的吧。