Realm:英文意思‘域’,在Shiro框架中,簡單理解它存儲(讀取配置、讀取數據庫、讀取內存中)用戶信息,而且它充當了Shiro與應用安全數據間的「橋樑」(「鏈接器」),當對某個用戶執行認證(登陸)和受權(用戶用有的角色、資源,或者說訪問控制)驗證時,Shiro會從應用配置的Realm中查找用戶以及權限信息。html
從它的在Shiro礦建中所起的做用來說,Realm至關因而一個安全相關的DAO:它封裝了數據源的鏈接細節,並在須要時將相關數據提供給Shiro。當配置Shiro時,你必須指定一個Realm(固然容許配置多個,指定多Realm驗證策略,多個Realm來工做),用於認證、受權。java
按照Realm中用戶和權限信息存儲方式不一樣,在Shiro框架內部定義瞭如下Realm:spring
1)IniRealm:將用戶、角色信息配置到*.ini文件中,使用IniRealm加載數據信息,提供認證、受權功能;數據庫
2)PropertiesRealm:將用戶、角色信息配置到*.properties文件中,使用PropertiesRealm加載數據信息,提供認證、受權功能;安全
3)jdbcRealm:將用戶、角色信息配置到關係型數據庫總,外部能夠指定DataSource信息,默認內部包含了認證、受權SQL,默認數據庫表:users、user_roles、roles_permissions;mvc
4)*Ldap*/ActiveDirectory*:LDAP目錄服務是由目錄數據庫和一套訪問協議組成的系統,其實就是把用戶、角色信息存儲到這樣的一個Ad系統,經過Ldap/Jndi/等方式鏈接讀取數據,至於shiro功能與上邊3種一致。app
5)自定義Realm:上圖中MyRealm就是我自定義的一個Realm,若是缺省的Realm不能知足需求時,咱們還能夠自定義數據源本身的Realm實現。框架
經過上邊Realm類結構圖,能夠看出IniRealm、PropertiesRealm、JdbcRealm等的最上層父類是 AuthorizingRealm,而它又繼承了AuthenticatingRealm,咱們查看類中定義:分佈式
AuthenticatingRealm.java中惟一抽象方法:(該方法提供身份認證使用,具體能夠參考缺省Realm的實現)ide
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
AuthorizingRealm.java中惟一抽象方法:(該方法給認證經過的用戶賦值權限、資源使用,具體能夠參考缺省Realm的實現)
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
1)身份認證:調用Realm#getAuthenticationinfo()方法,驗證用戶輸入的帳戶和密碼(內部調用Realm#doGetAuthenticationInfo()方法進行用戶認證),並返回與Realm數據對比驗證結果相關信息;查看AuthenticationRealm#getAuthenticationInfo()源碼:
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info = getCachedAuthenticationInfo(token); if (info == null) { //otherwise not cached, perform the lookup: info = doGetAuthenticationInfo(token); log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info); if (token != null && info != null) { cacheAuthenticationInfoIfPossible(token, info); } } else { log.debug("Using cached authentication info [{}] to perform credentials matching.", info); } if (info != null) { assertCredentialsMatch(token, info); } else { log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token); } return info; }
2)權限、資源獲取:調用Realm#getAuthorizationinfo()方法,獲取指定身份的權限(內部調用Realm#doGetAuthorizationInfo()方法進行制定身份的權限),並返回相關信息;查看AuthorizingRealm#getAuthorizationInfo()源碼:
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) { if (principals == null) { return null; } AuthorizationInfo info = null; if (log.isTraceEnabled()) { log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]"); } Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache(); if (cache != null) { if (log.isTraceEnabled()) { log.trace("Attempting to retrieve the AuthorizationInfo from cache."); } Object key = getAuthorizationCacheKey(principals); info = cache.get(key); if (log.isTraceEnabled()) { if (info == null) { log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]"); } else { log.trace("AuthorizationInfo found in cache for principals [" + principals + "]"); } } } if (info == null) { // Call template method if the info was not found in a cache info = doGetAuthorizationInfo(principals); // If the info is not null and the cache has been created, then cache the authorization info. if (info != null && cache != null) { if (log.isTraceEnabled()) { log.trace("Caching authorization info for principals: [" + principals + "]."); } Object key = getAuthorizationCacheKey(principals); cache.put(key, info); } } return info; }
3)斷定Token是否支持:調用Realm#supports()方法,驗證是否支持令牌(Token)。
備註:Token在Shiro中包含幾種類型:HostAuthenticationToken(主機驗證令牌),UsernamePasswordToken(帳戶密碼驗證令牌),RememberMeAuthenticationToken(記錄上次登陸用戶Token)。
Realm iniRealm = new IniRealm("classpath:shiroAuthorizer.ini"); DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(iniRealm); SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123"); // 調用 Realm#getAuthenticationinfo() subject.login(token); System.out.println("是否定證經過:" + subject.isAuthenticated()); // 調用 Realm#getAuthorizationinfo() System.out.println("是否受權admin角色:" + subject.hasRole("admin")); System.out.println("是否擁有user:update資源:" + subject.isPermitted("user:update")); System.out.println("是否擁有user:delete資源:" + subject.isPermitted("user:delete")); System.out.println("是否擁有user:create資源:" + subject.isPermitted("user:create")); subject.logout(); System.out.println("是否定證經過:" + subject.isAuthenticated()); System.out.println("是否受權admin角色:" + subject.hasRole("admin")); System.out.println("是否擁有user:update資源:" + subject.isPermitted("user:update")); System.out.println("是否擁有user:delete資源:" + subject.isPermitted("user:delete")); System.out.println("是否擁有user:create資源:" + subject.isPermitted("user:create"));
從Subject#login()代碼進行跟蹤,尋找Subject#login()底層調用的代碼以下:
DelegatingSubject#login()方法調用-》DefaultSecurityManager#login(),內部調用-》AbstractAuthenticator#authenticate()方法,內部調用-》ModularRealmAuthenticator#doAuthenticate()方法,該方法源碼以下:
/** * Subject#login最終底層調用的方法就是該方法 */ protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { assertRealmsConfigured(); Collection<Realm> realms = getRealms(); if (realms.size() == 1) { return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken); } else { return doMultiRealmAuthentication(realms, authenticationToken); } } /** * 單個Realm配置時,調用該法 */ protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) { if (!realm.supports(token)) { String msg = "Realm [" + realm + "] does not support authentication token [" + token + "]. Please ensure that the appropriate Realm implementation is " + "configured correctly or that the realm accepts AuthenticationTokens of this type."; throw new UnsupportedTokenException(msg); } AuthenticationInfo info = realm.getAuthenticationInfo(token); if (info == null) { String msg = "Realm [" + realm + "] was unable to find account data for the " + "submitted AuthenticationToken [" + token + "]."; throw new UnknownAccountException(msg); } return info; } /** * 多個Realm配置時,調用該方法 */ protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) { AuthenticationStrategy strategy = getAuthenticationStrategy(); AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token); if (log.isTraceEnabled()) { log.trace("Iterating through {} realms for PAM authentication", realms.size()); } for (Realm realm : realms) { aggregate = strategy.beforeAttempt(realm, token, aggregate); if (realm.supports(token)) { log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm); AuthenticationInfo info = null; Throwable t = null; try { info = realm.getAuthenticationInfo(token); } catch (Throwable throwable) { t = throwable; if (log.isDebugEnabled()) { String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:"; log.debug(msg, t); } } aggregate = strategy.afterAttempt(realm, token, info, aggregate, t); } else { log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token); } } aggregate = strategy.afterAllAttempts(token, aggregate); return aggregate; }
備註:
1)因爲Subject是一個接口類型,所以上邊代碼找的是 DelegatingSubject.java類中的代碼;
2)Subject的類結構圖:
從調用Subject#isPermitted()源碼能夠看出內部調用了Subject#getAuthorizationInfo()方法:
public boolean isPermitted(PrincipalCollection principals, Permission permission) { AuthorizationInfo info = getAuthorizationInfo(principals); return isPermitted(permission, info); }
從調用Subject#hasRole()源碼能夠看出內部調用了Subject#getAuthorizationInfo()方法:
public boolean hasRole(PrincipalCollection principal, String roleIdentifier) { AuthorizationInfo info = getAuthorizationInfo(principal); return hasRole(roleIdentifier, info); }
不過,getAuthorizationInfo 的執行調用方式包括上面的總共有三個:
1)subject.hasRole(「admin」) 或 subject.isPermitted(「admin」):本身去調用這個是否有什麼角色或者是否有什麼權限的時候;
2)@RequiresRoles(「admin」) :在Controller方法上加註解的時候;
3)<@shiro.hasPermission name = 「admin」>html code</@shiro.hasPermission>:在頁面上加shiro標籤的時候,即進這個頁面的時候掃描到有這個標籤的時候。
明。
參考資料:
《SSM整合shiro實現多用戶表多Realm統一登陸認證(大章附代碼)》
不錯的視屏教程,很實用:
https://www.bilibili.com/video/av22573274/?p=13
https://www.bilibili.com/video/av74805893?p=18 (針對java proj/springmvc整合/分佈式用法都有系列講解,值得推薦)
文章實戰:https://blog.csdn.net/bieleyang/article/category/7140250