閱讀源碼有助於陶冶情操,本文旨在簡單的分析shiro在Spring中的使用html
Shiro是一個強大易用的Java安全框架,提供了認證、受權、加密和會話管理等功能java
Realm->CachingRealm->AuthenticatingRealm->AuthorizingRealm
本文只針對以上關係進行講解,其他的實現類請自行查看源碼web
Realm提供了安全的訪問應用的相關實體類,好比用戶、角色、權限,對其中的訪問應用相應的認證或者受權操做。其提供的主要的方法爲AuthenticationInfo#getAuthenticationInfo
,涉及的內容是關於信息的認證,這主要由AuthencatingRealm
類實現算法
提供緩存功能,簡單看下其下的變量以及主要方法。spring
//設置是否容許緩存,構造函數中默認爲true private boolean cachingEnabled; //設置緩存管理器,須要另外引入 private CacheManager cacheManager;
//獲取principal對象,通常都是子類在執行受權操做賦予的 protected Object getAvailablePrincipal(PrincipalCollection principals) { Object primary = null; if (!CollectionUtils.isEmpty(principals)) { Collection thisPrincipals = principals.fromRealm(getName()); if (!CollectionUtils.isEmpty(thisPrincipals)) { primary = thisPrincipals.iterator().next(); } else { //no principals attributed to this particular realm. Fall back to the 'master' primary: primary = principals.getPrimaryPrincipal(); } } return primary; }
簡單分析下其主要私有變量以及方法apache
//與父類的cachingEnabled結合使用,在構造函數裏其默認是false private boolean authenticationCachingEnabled; //用戶憑證驗證類,好比校驗密碼是否一致 private CredentialsMatcher credentialsMatcher; //認證Token類,默認爲UsernamePasswordToken類 private Class<? extends AuthenticationToken> authenticationTokenClass;
//可見若是隻設置authenticationCachingEnable,其爲true,也會設置cachingEnable=true public void setAuthenticationCachingEnabled(boolean authenticationCachingEnabled) { this.authenticationCachingEnabled = authenticationCachingEnabled; if (authenticationCachingEnabled) { setCachingEnabled(true); } }
//可見是否應用緩存是根據authenticationCachingEnabled和cachingEnabled聯合判斷的 public boolean isAuthenticationCachingEnabled() { return this.authenticationCachingEnabled && isCachingEnabled(); }
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //獲取緩存中的認證信息,其中也會涉及到調用isAuthenticationCachingEnabled AuthenticationInfo info = getCachedAuthenticationInfo(token); if (info == null) { //otherwise not cached, perform the lookup: //調用doGetAuthenticationInfo方法,此處爲抽象類,供子類調用 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; }
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException ;
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException { //獲取私有屬性credentialsMatcher CredentialsMatcher cm = getCredentialsMatcher(); if (cm != null) { //校驗方法 if (!cm.doCredentialsMatch(token, info)) { //not successful - throw an exception to indicate this: String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials."; throw new IncorrectCredentialsException(msg); } } else { //可見credentialsMatcher屬性必需要設定,不然會拋異常 throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " + "credentials during authentication. If you do not wish for credentials to be examined, you " + "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance."); } }
其中的內容和其父類驗證抽象類基本類似,這裏就不贅述了,主要提下主要方法api
//受權 protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) { if (principals == null) { return null; } AuthorizationInfo info = null; if (log.isTraceEnabled()) { log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]"); } //是否引用cache 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; }
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
下述示例結合了受權與驗證,即繼承AuthorizingRealm便可瀏覽器
<!--緩存管理器--> <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean> <!-- 憑證匹配器 --> <bean id="credentialsMatcher" class="com.jing.test.cas.admin.core.shiro.RetryLimitHashedCredentialsMatcher"> <constructor-arg ref="cacheManager"/> <property name="hashAlgorithmName" value="md5"/> <property name="hashIterations" value="2"/> <property name="storedCredentialsHexEncoded" value="true"/> </bean> <!--密碼處理類--> <bean id="passwordService" class="com.jing.test.cas.admin.core.shiro.ShiroPasswordService"></bean> <!-- Realm實現 --> <bean id="shiroDBRealm" class="com.jing.test.cas.admin.core.shiro.ShiroDBRealm"> <property name="credentialsMatcher" ref="credentialsMatcher"/> <property name="cachingEnabled" value="false"/> <property name="shiroPasswordService" ref="passwordService"/> <property name="shiroUserService" ref="shiroUserService"/> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="shiroDBRealm"/> <property name="sessionManager" ref="sessionManager"/> </bean> <!-- 至關於調用SecurityUtils.setSecurityManager(securityManager) --> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/> <property name="arguments" ref="securityManager"/> </bean> <!-- Shiro的Web過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login"/> <property name="successUrl" value="/index.html"/> <property name="unauthorizedUrl" value="/403.html"/> <property name="filters"> <util:map> <entry key="authc" value-ref="formAuthenticationFilter"/> <entry key="perm" value-ref="permissionsAuthorizationFilter"/> <entry key="captcha" value-ref="captchaValidateFilter"/> <entry key="sysUser" value-ref="sysUserFilter"/> <entry key="user" value-ref="userFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <!-- 若是不該用acm cas方案則添加 /logout=logout --> <value> /ossmanager/api/** = anon /test/** = anon /login = captcha,authc /logout=logout /index = anon /jcaptcha.jpeg = anon /403.html = anon /login.html = anon /favicon.ico = anon /static/** = anon /index.html=user,sysUser /welcome.html=user,sysUser /admin/user/modifyPwd.html=user,sysUser /admin/user/updatePassword=user,sysUser /admin/user/role/list=user,sysUser /** = user,sysUser,perm </value> </property> </bean> <!-- Shiro生命週期處理器--> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
package com.jing.test.cas.admin.core.shiro; import com.jing.test.cas.admin.core.exceptions.BizException; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; import java.util.Set; /** *Realm接口實現類 */ public class ShiroDBRealm extends AuthorizingRealm { /** * 日誌對象 */ private static final Logger logger = LoggerFactory.getLogger(ShiroDBRealm.class); /** * 帳戶禁用 */ private static final String USER_STATUS_FORBIDDEN = "1"; /** * 權限相關用戶服務接口 */ private IShiroUserService shiroUserService; /** * 密碼服務類 加密做用 */ private ShiroPasswordService shiroPasswordService; /** * 受權 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 由於非正常退出,即沒有顯式調用 SecurityUtils.getSubject().logout() // (多是關閉瀏覽器,或超時),但此時緩存依舊存在(principals),因此會本身跑到受權方法裏。 if (!SecurityUtils.getSubject().isAuthenticated()) { doClearCache(principalCollection); SecurityUtils.getSubject().logout(); return null; } ShiroUser shiroUser = (ShiroUser)principalCollection.getPrimaryPrincipal(); String userName = shiroUser.getUserName(); if(StringUtils.isNotBlank(userName)){ SimpleAuthorizationInfo sazi = new SimpleAuthorizationInfo(); try { Set<String> roleIds= shiroUserService.getRoles(userName); for (String roleId: roleIds){ shiroUser.setRoleId(roleId); } sazi.addRoles(roleIds); sazi.addStringPermissions(shiroUserService.getPermissions(userName)); return sazi; } catch (Exception e) { logger.error(e.getMessage(),e); } } return null; } /** * 認證 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; ShiroUser shiroUser = shiroUserService.getShiroUser(token.getUsername()); checkUserStatus(shiroUser); if(shiroUser != null){ SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(shiroUser,shiroUser.getPassword(),ByteSource.Util.bytes(shiroUser.getUserName()+shiroPasswordService.getPublicSalt()),getName()); return authenticationInfo; } return null; } /** * 檢查用戶狀態 * @param shiroUser */ private void checkUserStatus(ShiroUser shiroUser) { if(StringUtils.equalsIgnoreCase(shiroUser.getUserStatus(),USER_STATUS_FORBIDDEN)){ throw new ForbiddenException("用戶已被禁用"); } } /** * 初始化方法 * 設定Password校驗的Hash算法與迭代次數. **/ public void initCredentialsMatcher() { HashedCredentialsMatcher matcher = new HashedCredentialsMatcher( shiroPasswordService.getHashAlgorithm()); matcher.setHashIterations(shiroPasswordService.getHashInterations()); setCredentialsMatcher(matcher); } public void setShiroUserService(IShiroUserService shiroUserService) { this.shiroUserService = shiroUserService; } public void setShiroPasswordService(ShiroPasswordService shiroPasswordService) { this.shiroPasswordService = shiroPasswordService; } }
受權與驗證的邏輯作了簡單的瞭解,其中受權可經過subject.isPermited()等方法調用;驗證則可經過subject.login()等方法來調用緩存