一、shiro基礎概念
Authentication:身份認證 / 登錄,驗證用戶是不是擁有相應的身份;
Authorization:授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具有某個權限;
Session Manager:會話管理,即用戶登錄后就是一次會話,在沒有退出之前,它的所有信息都在會話中;會話可以是普通 JavaSE 環境的,也可以是如 Web 環境的;
Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;
Web Support:Web 支持,可以非常容易的集成到 Web 環境;
Caching:緩存,比如用戶登錄后,其用戶信息、擁有的角色 / 權限不必每次去查,這樣可以提高效率;
Concurrency:shiro 支持多線程應用的並發驗證,即如在一個線程中開啟另一個線程,能把權限自動傳播過去;
Testing:提供測試支持;
Run As:允許一個用戶假裝為另一個用戶(如果他們允許)的身份進行訪問;
Remember Me:記住我,這個是非常常見的功能,即一次登錄后,下次再來的話不用登錄了。
記住一點,Shiro 不會去維護用戶、維護權限;這些需要我們自己去設計 / 提供;然后通過相應的接口注入給 Shiro 即可。
二、應用程序使用 Shiro的原理
從應用程序角度的來觀察如何使用 Shiro 完成工作,如圖
可以看到:應用代碼直接交互的對象是 Subject,也就是說 Shiro 的對外 API 核心就是 Subject;其每個 API 的含義:
Subject:主體,代表了當前 “用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是 Subject,如網絡爬蟲,機器人等;即一個抽象概念;所有 Subject 都綁定到 SecurityManager,與 Subject 的所有交互都會委托給 SecurityManager;可以把 Subject 認為是一個門面;SecurityManager 才是實際的執行者;
SecurityManager:安全管理器;即所有與安全有關的操作都會與 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它負責與后邊介紹的其他組件進行交互,如果學習過 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
Realm:域,Shiro 從從 Realm 獲取安全數據(如用戶、角色、權限),就是說 SecurityManager 要驗證用戶身份,那么它需要從 Realm 獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從 Realm 得到用戶相應的角色 / 權限進行驗證用戶是否能進行操作;可以把 Realm 看成 DataSource,即安全數據源。
也就是說對於我們而言,最簡單的一個 Shiro 應用:
-
應用代碼通過 Subject 來進行認證和授權,而 Subject 又委托給 SecurityManager;
- 我們需要給 Shiro 的 SecurityManager 注入 Realm,從而讓 SecurityManager 能得到合法的用戶及其權限進行判斷。
從以上也可以看出,Shiro 不提供維護用戶 / 權限,而是通過 Realm 讓開發人員自己注入。
三、認證
身份認證流程,如圖
流程如下:
- 首先調用 Subject.login(token) 進行登錄,其會自動委托給 Security Manager,調用之前必須通過 SecurityUtils.setSecurityManager() 設置;
- SecurityManager 負責真正的身份驗證邏輯;它會委托給 Authenticator 進行身份驗證;
- Authenticator 才是真正的身份驗證者,Shiro API 中核心的身份認證入口點,此處可以自定義插入自己的實現;
- Authenticator 可能會委托給相應的 AuthenticationStrategy 進行多 Realm 身份驗證,默認 ModularRealmAuthenticator 會調用 AuthenticationStrategy 進行多 Realm 身份驗證;
- Authenticator 會把相應的 token 傳入 Realm,從 Realm 獲取身份驗證信息,如果沒有返回 / 拋出異常表示身份驗證失敗了。此處可以配置多個 Realm,將按照相應的順序及策略進行訪問。
login接口controller
UsernamePasswordToken token = new UsernamePasswordToken(username, password); try{ token.setRememberMe(1 == rememberMe); Subject subject = SecurityUtils.getSubject(); subject.login(token); } catch (LockedAccountException e) { token.clear(); return ResultUtil.error("用戶已經被鎖定不能登錄,請聯系管理員!"); } catch (AuthenticationException e) { token.clear(); return ResultUtil.error("用戶名或者密碼錯誤!"); }
自定義realm的認證流程
//認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("認證入口。。。。"); //獲取用戶的輸入的賬號. String username = (String)token.getPrincipal(); //得到密碼 String password = new String((char[])token.getCredentials()); User user = userService.selectByUsername(username); if(user==null) { throw new UnknownAccountException(); } if (CoreConst.STATUS_INVALID.equals(user.getStatus())) { // 帳號鎖定 throw new LockedAccountException(); } HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // 把ip放入user存入redis緩存里 user.setLoginIpAddress(IpUtil.getIpAddr(request)); SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( user, user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName() ); return authenticationInfo; }
四、授權
授權流程,如圖
流程如下:
- 首先調用
Subject.isPermitted*/hasRole*
接口,其會委托給 SecurityManager,而 SecurityManager 接着會委托給 Authorizer; - Authorizer 是真正的授權者,如果我們調用如 isPermitted(“user:view”),其首先會通過 PermissionResolver 把字符串轉換成相應的 Permission 實例;
- 在進行授權之前,其會調用相應的 Realm 獲取 Subject 相應的角色/權限用於匹配傳入的角色/權限;
- Authorizer 會判斷 Realm 的角色/權限是否和傳入的匹配,如果有多個 Realm,會委托給 ModularRealmAuthorizer 進行循環判斷,如果匹配如
isPermitted*/hasRole*
會返回 true,否則返回 false 表示授權失敗。繼承 AuthorizingRealm 而不是實現 Realm 接口; - 推薦使用 AuthorizingRealm,因為: AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token):表示獲取身份驗證信息;AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals):表示根據用戶身份獲取授權信息。這種方式的好處是當只需要身份驗證時只需要獲取身份驗證信息而不需要獲取授權信息。
自定義realm的授權流程
//授權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("授權入口。。。。"); if(principals == null){ throw new AuthorizationException("principals should not be null"); } User user = (User) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(roleService.findRoleByUserId(user.getUserId())); info.setStringPermissions(permissionService.findPermsByUserId(user.getUserId())); return info; }
五、多realm使用
六、自定義用戶個性認證
七、自定義過濾鏈
八、緩存機制