Java-Shiro(四):Shiro Realm講解(一)Realm介紹

Realm簡介

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功能

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#getAuthenticationinfo()、何時調用 Realm#getAuthorizationinfo()?

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"));

1)何時調用身份認證(Realm#getAuthenticationinfo())

從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的類結構圖:

2)何時調用受權( Realm#getAuthorizationinfo())

從調用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標籤的時候,即進這個頁面的時候掃描到有這個標籤的時候。
明。

參考資料:

Shiro 中的 Realm

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

相關文章
相關標籤/搜索