接下來的幾天,咱們開講Shiro,從入門到分析、集成、單點登陸整合等幾篇。今天咱們先來認識一下Shiro吧~html
其實Shiro框架並不難,我梳理了一下,你只須要學會如下內容基本就足夠了:git
接下來我會分爲幾篇文章分別去介紹,這篇咱們先來了解一下shiro的一些基礎知識,以及登陸受權邏輯。github
在Web系統中咱們常常要涉及到權限問題,例如不一樣角色的人登陸系統,他操做的功能、按鈕、菜單是各不相同的,這就是所謂的權限。web
而構建一個互聯網應用,權限校驗管理是很重要的安全措施,這其中主要包含:面試
Shiro對以上功能都進行了很好的支持,它能夠很是容易的開發出足夠好的應用。Shiro能夠幫助咱們完成:認證、受權、加密、會話管理、與Web集成、緩存等。並且Shiro的API也是很是簡單。redis
官方源碼:https://github.com/apache/shiroapache
從上圖能夠看出,Security Manager是Shiro的核心管理器,認證受權會話緩存等都是在其內部完成,而後會委託給具體的組件來處理,好比認證過程委託給Authenticator,受權委託給Authorizer組件。因此,整理仍是比較清晰,源代碼也容易追蹤。緩存
咱們來具體聊聊全部的組件:安全
Subject: 主體,能夠看到主體能夠是任何能夠與應用交互的「用戶」;session
SecurityManager: Shiro的心臟;全部具體的交互都經過SecurityManager進行控制;負責全部Subject、且負責進行認證和受權、及會話、緩存的管理。
Realm: 能夠有1個或多個Realm,主要提供認證和受權的數據;
Session: Shiro提供一個權限的企業級Session解決方案,session的生命週期都在SessionManager中進行管理。
SessionManager: shiro的會話管理器;
SessionDAO: 用於會話的CRUD,好比存儲到ehcache或者redis中的會話增刪改查;
CacheManager: 緩存控制器,來管理如用戶、角色、權限等的緩存的;由於這些數據基本上不多去改變,放到緩存中後能夠提升訪問的性能
Cryptography: 密碼模塊,Shiro提升了一些常見的加密組件用於如密碼加密/解密的。
官網例子:http://shiro.apache.org/tutorial.html
剛入門Shiro的同窗,真的須要去看看這個官方例子,你能夠更加深刻了解Shiro的權限校驗流程。我仍是貼一下代碼吧,一些同窗比較懶:
# ----------------------------------------------------------------------------- # 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
上面代碼中,root = secret, admin
表示,用戶名root,密碼secret,角色是admin;schwartz = lightsaber:*
表示角色schwartz擁有權限lightsaber:*。你其實能夠把這個文件當作一個Realm,其實就是shiro默認的IniRealm。
public class Tutorial { private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class); public static void main(String[] args) { log.info("My First Apache Shiro Application"); Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // get the currently executing user: Subject currentUser = SecurityUtils.getSubject(); // Do some stuff with a Session (no need for a web or EJB container!!!) 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 + "]"); } // let's login the current user so we can check against roles and permissions: 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 more exceptions here (maybe custom ones specific to your application? catch (AuthenticationException ae) { //unexpected condition? error? } } //say who they are: //print their identifying principal (in this case, a username): log.info("User [" + currentUser.getPrincipal() + "] logged in successfully."); //test a role: if (currentUser.hasRole("schwartz")) { log.info("May the Schwartz be with you!"); } else { log.info("Hello, mere mortal."); } //test a typed permission (not instance-level) 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."); } //a (very powerful) Instance Level permission: 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!"); } //all done - log out! currentUser.logout(); System.exit(0); } }
從上面的實例中,咱們能夠總結一下經常使用的API:
#獲取當前用戶 Subject currentUser = SecurityUtils.getSubject(); #判斷用戶已經認證 currentUser.isAuthenticated() #用戶登陸憑證 UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); #記住我 token.setRememberMe(true); #登錄校驗 currentUser.login(token); #判斷是否有角色權限 currentUser.hasRole("schwartz") #判斷是否有資源操做權限 currentUser.isPermitted("lightsaber:wield") #登出 currentUser.logout();
其實稍微梳理一下,能夠發現上面代碼主要有兩個步驟:
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); currentUser.login(token); currentUser.logout();
currentUser.hasRole("schwartz") currentUser.isPermitted("winnebago:drive:eagle5")
接下來,咱們去探討一下shiro的認證與受權流程,並從源碼層去解析一下shiro各個組件之間的關係。
上面圖片中,根據序號,其實咱們大概能猜出裏shiro的認證流程:
咱們從login方法開始debug一下流程,用簡要方式追蹤shiro源碼的認證邏輯:
currentUser.login(token); | Subject subject = this.securityManager.login(this, token); | AuthenticationInfo info = this.authenticate(token); | this.authenticator.authenticate(token); | AuthenticationInfo info = this.doAuthenticate(token); | Collection<Realm> realms = this.getRealms(); doSingleRealmAuthentication(realm, token); | AuthenticationInfo info = realm.getAuthenticationInfo(token); | AuthenticationInfo info = realm.doGetAuthenticationInfo(token);
ok,一條線下來,從login到委託給authenticator,再最後調用realm的doGetAuthenticationInfo方法。
因此,從源碼上來看,若是要實現shiro的認證邏輯,至少要準備一個Realm組件、和初始化securityManager組件。
從上圖中,咱們能夠知道受權流程以下:
追蹤一下源碼以下:
currentUser.hasRole("schwartz") | this.securityManager.hasRole(this.getPrincipals(), roleIdentifier) | this.authorizer.hasRole(principals, roleIdentifier) | AuthorizationInfo info = this.getAuthorizationInfo(principal); return info.getRoles().contains(roleIdentifier) | info = this.doGetAuthorizationInfo(principals);(realm)
因此shiro判斷用戶是否有權限首先會從realm中獲取用戶所擁有的權限角色信息,而後再匹配當前的角色或權限是否包含,從而斷定用戶是否有權限!
說到權限,不少人天然會想起權限系統,涉及到幾個關鍵對象:
經過這幾個要素,能夠設計出比較合理的權限系統。
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole(「admin」)) { //有權限 } else { //無權限 }
@RequiresRoles("admin") public void hello() { //有權限 }
<shiro:hasRole name="admin"> <!— 有權限 —> </shiro:hasRole>
jsp頁面引入shiro標籤
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
@Autowired private SessionDAO sessionDAO; //獲取會話數量 int size = sessionDAO.getActiveSessions().size()
//強制退出 Session session = sessionDAO.readSession(subject.getSession().getId()); sessionDAO.delete(session); // logout,可做爲強制退出 subject.logout(); Assert.isTrue(!subject.isAuthenticated());
ok,感受是高度極簡的一篇文章,主要把重要的組件和登陸、受權幾個流程搞清楚以後,其實shiro基本已經學會了,後面咱們再學一下shiro的幾個主要內置過濾器怎麼使用,如何集成SpringBoot,基本就差很少了。
推薦閱讀:
分享一套SpringBoot開發博客系統源碼,以及完整開發文檔!速度保存!