說在前面 (原文連接: https://blog.csdn.net/qq_34021712/article/details/80774649)
原本的整合過程是順着博客的順序來的,越往下,集成的越多,因爲以前是使用ehcache緩存,如今改成redis,限制登陸人數 以及 限制登陸次數等 都須要改動,本篇爲了簡單,目前先將這兩個功能下線,配置暫時是註銷的,原類保存,在下篇博客中改。
還有以前是使用SessionListener監聽session建立來統計在線人數,在本篇中也將改成統計redis中的key數目。
若是是單機,使用ehcache是最快的,項目通常都不是單節點,爲了方便以後使用sso單點登陸,以及多節點部署,因此使用shiro整合redis。這裏有一個開源項目,git地址爲:https://github.com/alexxiyang/shiro-redis 在此感謝做者無私奉獻。javascript
shiro用redis實現緩存須要重寫cache、cacheManager、SessionDAO和初始化redis配置。css
<!-- 整合shiro框架 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <!-- shiro-thymeleaf 2.0.0--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>1.2.1</version> </dependency> <!-- shiro-redis --> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>3.1.0</version> </dependency>
package com.springboot.test.shiro.config; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import com.springboot.test.shiro.config.shiro.*; import org.apache.shiro.codec.Base64; import org.apache.shiro.session.SessionListener; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator; import org.apache.shiro.session.mgt.eis.SessionDAO; import org.apache.shiro.session.mgt.eis.SessionIdGenerator; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.crazycake.shiro.RedisCacheManager; import org.crazycake.shiro.RedisManager; import org.crazycake.shiro.RedisSessionDAO; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.web.servlet.ErrorPage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import javax.servlet.Filter; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Properties; /** * @author: wangsaichao * @date: 2018/5/10 * @description: Shiro配置 */ @Configuration public class ShiroConfig { /** * ShiroFilterFactoryBean 處理攔截資源文件問題。 * 注意:初始化ShiroFilterFactoryBean的時候須要注入:SecurityManager * Web應用中,Shiro可控制的Web請求必須通過Shiro主過濾器的攔截 * @param securityManager * @return */ @Bean(name = "shirFilter") public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //必須設置 SecurityManager,Shiro的核心安全接口 shiroFilterFactoryBean.setSecurityManager(securityManager); //這裏的/login是後臺的接口名,非頁面,若是不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setLoginUrl("/"); //這裏的/index是後臺的接口名,非頁面,登陸成功後要跳轉的連接 shiroFilterFactoryBean.setSuccessUrl("/index"); //未受權界面,該配置無效,並不會進行頁面跳轉 shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); //自定義攔截器限制併發人數,參考博客: LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>(); //限制同一賬號同時在線的個數 //filtersMap.put("kickout", kickoutSessionControlFilter()); //統計登陸人數 shiroFilterFactoryBean.setFilters(filtersMap); // 配置訪問權限 必須是LinkedHashMap,由於它必須保證有序 // 過濾鏈定義,從上向下順序執行,通常將 /**放在最爲下邊 --> : 這是一個坑,一不當心代碼就很差使了 LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //配置不登陸能夠訪問的資源,anon 表示資源均可以匿名訪問 //配置記住我或認證經過能夠訪問的地址 filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/img/**", "anon"); filterChainDefinitionMap.put("/druid/**", "anon"); //解鎖用戶專用 測試用的 filterChainDefinitionMap.put("/unlockAccount","anon"); filterChainDefinitionMap.put("/Captcha.jpg","anon"); //logout是shiro提供的過濾器 filterChainDefinitionMap.put("/logout", "logout"); //此時訪問/user/delete須要delete權限,在自定義Realm中爲用戶受權。 //filterChainDefinitionMap.put("/user/delete", "perms[\"user:delete\"]"); //其餘資源都須要認證 authc 表示須要認證才能進行訪問 user表示配置記住我或認證經過能夠訪問的地址 //若是開啓限制同一帳號登陸,改成 .put("/**", "kickout,user"); filterChainDefinitionMap.put("/**", "user"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 配置核心安全事務管理器 * @return */ @Bean(name="securityManager") public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //設置自定義realm. securityManager.setRealm(shiroRealm()); //配置記住我 securityManager.setRememberMeManager(rememberMeManager()); //配置redis緩存 securityManager.setCacheManager(cacheManager()); //配置自定義session管理,使用redis securityManager.setSessionManager(sessionManager()); return securityManager; } /** * 配置Shiro生命週期處理器 * @return */ @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 身份認證realm; (這個須要本身寫,帳號密碼校驗;權限等) * @return */ @Bean public ShiroRealm shiroRealm(){ ShiroRealm shiroRealm = new ShiroRealm(); shiroRealm.setCachingEnabled(true); //啓用身份驗證緩存,即緩存AuthenticationInfo信息,默認false shiroRealm.setAuthenticationCachingEnabled(true); //緩存AuthenticationInfo信息的緩存名稱 在ehcache-shiro.xml中有對應緩存的配置 shiroRealm.setAuthenticationCacheName("authenticationCache"); //啓用受權緩存,即緩存AuthorizationInfo信息,默認false shiroRealm.setAuthorizationCachingEnabled(true); //緩存AuthorizationInfo信息的緩存名稱 在ehcache-shiro.xml中有對應緩存的配置 shiroRealm.setAuthorizationCacheName("authorizationCache"); //配置自定義密碼比較器 //shiroRealm.setCredentialsMatcher(retryLimitHashedCredentialsMatcher()); return shiroRealm; } /** * 必須(thymeleaf頁面使用shiro標籤控制按鈕是否顯示) * 未引入thymeleaf包,Caused by: java.lang.ClassNotFoundException: org.thymeleaf.dialect.AbstractProcessorDialect * @return */ @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); } /** * 開啓shiro 註解模式 * 能夠在controller中的方法前加上註解 * 如 @RequiresPermissions("userInfo:add") * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 解決: 無權限頁面不跳轉 shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized") 無效 * shiro的源代碼ShiroFilterFactoryBean.Java定義的filter必須知足filter instanceof AuthorizationFilter, * 只有perms,roles,ssl,rest,port纔是屬於AuthorizationFilter,而anon,authcBasic,auchc,user是AuthenticationFilter, * 因此unauthorizedUrl設置後頁面不跳轉 Shiro註解模式下,登陸失敗與沒有權限都是經過拋出異常。 * 而且默認並無去處理或者捕獲這些異常。在SpringMVC下須要配置捕獲相應異常來通知用戶信息 * @return */ @Bean public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { SimpleMappingExceptionResolver simpleMappingExceptionResolver=new SimpleMappingExceptionResolver(); Properties properties=new Properties(); //這裏的 /unauthorized 是頁面,不是訪問的路徑 properties.setProperty("org.apache.shiro.authz.UnauthorizedException","/unauthorized"); properties.setProperty("org.apache.shiro.authz.UnauthenticatedException","/unauthorized"); simpleMappingExceptionResolver.setExceptionMappings(properties); return simpleMappingExceptionResolver; } /** * 解決spring-boot Whitelabel Error Page * @return */ @Bean public EmbeddedServletContainerCustomizer containerCustomizer() { return new EmbeddedServletContainerCustomizer() { @Override public void customize(ConfigurableEmbeddedServletContainer container) { ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthorized.html"); ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404.html"); ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html"); container.addErrorPages(error401Page, error404Page, error500Page); } }; } /** * cookie對象;會話Cookie模板 ,默認爲: JSESSIONID 問題: 與SERVLET容器名衝突,從新定義爲sid或rememberMe,自定義 * @return */ @Bean public SimpleCookie rememberMeCookie(){ //這個參數是cookie的名稱,對應前端的checkbox的name = rememberMe SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); //setcookie的httponly屬性若是設爲true的話,會增長對xss防禦的安全係數。它有如下特色: //setcookie()的第七個參數 //設爲true後,只能經過http訪問,javascript沒法訪問 //防止xss讀取cookie simpleCookie.setHttpOnly(true); simpleCookie.setPath("/"); //<!-- 記住我cookie生效時間30天 ,單位秒;--> simpleCookie.setMaxAge(2592000); return simpleCookie; } /** * cookie管理對象;記住我功能,rememberMe管理器 * @return */ @Bean public CookieRememberMeManager rememberMeManager(){ CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); //rememberMe cookie加密的密鑰 建議每一個項目都不同 默認AES算法 密鑰長度(128 256 512 位) cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag==")); return cookieRememberMeManager; } /** * FormAuthenticationFilter 過濾器 過濾記住我 * @return */ @Bean public FormAuthenticationFilter formAuthenticationFilter(){ FormAuthenticationFilter formAuthenticationFilter = new FormAuthenticationFilter(); //對應前端的checkbox的name = rememberMe formAuthenticationFilter.setRememberMeParam("rememberMe"); return formAuthenticationFilter; } /** * shiro緩存管理器; * 須要添加到securityManager中 * @return */ @Bean public RedisCacheManager cacheManager(){ RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); //redis中針對不一樣用戶緩存 redisCacheManager.setPrincipalIdFieldName("username"); //用戶權限信息緩存時間 redisCacheManager.setExpire(200000); return redisCacheManager; } /** * 讓某個實例的某個方法的返回值注入爲Bean的實例 * Spring靜態注入 * @return */ @Bean public MethodInvokingFactoryBean getMethodInvokingFactoryBean(){ MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean(); factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager"); factoryBean.setArguments(new Object[]{securityManager()}); return factoryBean; } /** * 配置session監聽 * @return */ @Bean("sessionListener") public ShiroSessionListener sessionListener(){ ShiroSessionListener sessionListener = new ShiroSessionListener(); return sessionListener; } /** * 配置會話ID生成器 * @return */ @Bean public SessionIdGenerator sessionIdGenerator() { return new JavaUuidSessionIdGenerator(); } @Bean public RedisManager redisManager(){ RedisManager redisManager = new RedisManager(); redisManager.setHost("127.0.0.1"); redisManager.setPort(6379); redisManager.setPassword("123456"); return redisManager; } /** * SessionDAO的做用是爲Session提供CRUD並進行持久化的一個shiro組件 * MemorySessionDAO 直接在內存中進行會話維護 * EnterpriseCacheSessionDAO 提供了緩存功能的會話維護,默認狀況下使用MapCache實現,內部使用ConcurrentHashMap保存緩存的會話。 * @return */ @Bean public SessionDAO sessionDAO() { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); //session在redis中的保存時間,最好大於session會話超時時間 redisSessionDAO.setExpire(12000); return redisSessionDAO; } /** * 配置保存sessionId的cookie * 注意:這裏的cookie 不是上面的記住我 cookie 記住我須要一個cookie session管理 也須要本身的cookie * 默認爲: JSESSIONID 問題: 與SERVLET容器名衝突,從新定義爲sid * @return */ @Bean("sessionIdCookie") public SimpleCookie sessionIdCookie(){ //這個參數是cookie的名稱 SimpleCookie simpleCookie = new SimpleCookie("sid"); //setcookie的httponly屬性若是設爲true的話,會增長對xss防禦的安全係數。它有如下特色: //setcookie()的第七個參數 //設爲true後,只能經過http訪問,javascript沒法訪問 //防止xss讀取cookie simpleCookie.setHttpOnly(true); simpleCookie.setPath("/"); //maxAge=-1表示瀏覽器關閉時失效此Cookie simpleCookie.setMaxAge(-1); return simpleCookie; } /** * 配置會話管理器,設定會話超時及保存 * @return */ @Bean("sessionManager") public SessionManager sessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); Collection<SessionListener> listeners = new ArrayList<SessionListener>(); //配置監聽 listeners.add(sessionListener()); sessionManager.setSessionListeners(listeners); sessionManager.setSessionIdCookie(sessionIdCookie()); sessionManager.setSessionDAO(sessionDAO()); sessionManager.setCacheManager(cacheManager()); //全局會話超時時間(單位毫秒),默認30分鐘 暫時設置爲10秒鐘 用來測試 sessionManager.setGlobalSessionTimeout(1800000); //是否開啓刪除無效的session對象 默認爲true sessionManager.setDeleteInvalidSessions(true); //是否開啓定時調度器進行檢測過時session 默認爲true sessionManager.setSessionValidationSchedulerEnabled(true); //設置session失效的掃描時間, 清理用戶直接關閉瀏覽器形成的孤立會話 默認爲 1個小時 //設置該屬性 就不須要設置 ExecutorServiceSessionValidationScheduler 底層也是默認自動調用ExecutorServiceSessionValidationScheduler //暫時設置爲 5秒 用來測試 sessionManager.setSessionValidationInterval(3600000); //取消url 後面的 JSESSIONID sessionManager.setSessionIdUrlRewritingEnabled(false); return sessionManager; } /** * 併發登陸控制 * @return */ // @Bean // public KickoutSessionControlFilter kickoutSessionControlFilter(){ // KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter(); // //用於根據會話ID,獲取會話進行踢出操做的; // kickoutSessionControlFilter.setSessionManager(sessionManager()); // //使用cacheManager獲取相應的cache來緩存用戶登陸的會話;用於保存用戶—會話之間的關係的; // kickoutSessionControlFilter.setCacheManager(cacheManager()); // //是否踢出後來登陸的,默認是false;即後者登陸的用戶踢出前者登陸的用戶; // kickoutSessionControlFilter.setKickoutAfter(false); // //同一個用戶最大的會話數,默認1;好比2的意思是同一個用戶容許最多同時兩我的登陸; // kickoutSessionControlFilter.setMaxSession(1); // //被踢出後重定向到的地址; // kickoutSessionControlFilter.setKickoutUrl("/login?kickout=1"); // return kickoutSessionControlFilter; // } /** * 配置密碼比較器 * @return */ // @Bean("credentialsMatcher") // public RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher(){ // RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher(cacheManager()); // // //若是密碼加密,能夠打開下面配置 // //加密算法的名稱 // //retryLimitHashedCredentialsMatcher.setHashAlgorithmName("MD5"); // //配置加密的次數 // //retryLimitHashedCredentialsMatcher.setHashIterations(1024); // //是否存儲爲16進制 // //retryLimitHashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); // // return retryLimitHashedCredentialsMatcher; // } }
package com.springboot.test.shiro.config.shiro; import com.springboot.test.shiro.modules.user.dao.PermissionMapper; import com.springboot.test.shiro.modules.user.dao.RoleMapper; import com.springboot.test.shiro.modules.user.dao.entity.Permission; import com.springboot.test.shiro.modules.user.dao.entity.Role; import com.springboot.test.shiro.modules.user.dao.UserMapper; import com.springboot.test.shiro.modules.user.dao.entity.User; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; 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.springframework.beans.factory.annotation.Autowired; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * @author: wangsaichao * @date: 2018/5/10 * @description: 在Shiro中,最終是經過Realm來獲取應用程序中的用戶、角色及權限信息的 * 在Realm中會直接從咱們的數據源中獲取Shiro須要的驗證信息。能夠說,Realm是專用於安全框架的DAO. */ public class ShiroRealm extends AuthorizingRealm { @Autowired private UserMapper userMapper; @Autowired private RoleMapper roleMapper; @Autowired private PermissionMapper permissionMapper; /** * 驗證用戶身份 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //獲取用戶名密碼 第一種方式 //String username = (String) authenticationToken.getPrincipal(); //String password = new String((char[]) authenticationToken.getCredentials()); //獲取用戶名 密碼 第二種方式 UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken; String username = usernamePasswordToken.getUsername(); String password = new String(usernamePasswordToken.getPassword()); //從數據庫查詢用戶信息 User user = this.userMapper.findByUserName(username); //能夠在這裏直接對用戶名校驗,或者調用 CredentialsMatcher 校驗 if (user == null) { throw new UnknownAccountException("用戶名或密碼錯誤!"); } //這裏將 密碼對比 註銷掉,不然 沒法鎖定 要將密碼對比 交給 密碼比較器 //if (!password.equals(user.getPassword())) { // throw new IncorrectCredentialsException("用戶名或密碼錯誤!"); //} if ("1".equals(user.getState())) { throw new LockedAccountException("帳號已被鎖定,請聯繫管理員!"); } //調用 CredentialsMatcher 校驗 還須要建立一個類 繼承CredentialsMatcher 若是在上面校驗了,這個就不須要了 //配置自定義權限登陸器 參考博客: SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName()); return info; } /** * 受權用戶權限 * 受權的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>標籤的時候調用的 * 它會去檢測shiro框架中的權限(這裏的permissions)是否包含有該標籤的name值,若是有,裏面的內容顯示 * 若是沒有,裏面的內容不予顯示(這就完成了對於權限的認證.) * * shiro的權限受權是經過繼承AuthorizingRealm抽象類,重載doGetAuthorizationInfo(); * 當訪問到頁面的時候,連接配置了相應的權限或者shiro標籤纔會執行此方法不然不會執行 * 因此若是隻是簡單的身份認證沒有權限的控制的話,那麼這個方法能夠不進行實現,直接返回null便可。 * * 在這個方法中主要是使用類:SimpleAuthorizationInfo 進行角色的添加和權限的添加。 * authorizationInfo.addRole(role.getRole()); authorizationInfo.addStringPermission(p.getPermission()); * * 固然也能夠添加set集合:roles是從數據庫查詢的當前用戶的角色,stringPermissions是從數據庫查詢的當前用戶對應的權限 * authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(stringPermissions); * * 就是說若是在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "perms[權限添加]"); * 就說明訪問/add這個連接必需要有「權限添加」這個權限才能夠訪問 * * 若是在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "roles[100002],perms[權限添加]"); * 就說明訪問/add這個連接必需要有 "權限添加" 這個權限和具備 "100002" 這個角色才能夠訪問 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("查詢權限方法調用了!!!"); //獲取用戶 User user = (User) SecurityUtils.getSubject().getPrincipal(); //獲取用戶角色 Set<Role> roles =this.roleMapper.findRolesByUserId(user.getUid()); //添加角色 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); for (Role role : roles) { authorizationInfo.addRole(role.getRole()); } //獲取用戶權限 Set<Permission> permissions = this.permissionMapper.findPermissionsByRoleId(roles); //添加權限 for (Permission permission:permissions) { authorizationInfo.addStringPermission(permission.getPermission()); } return authorizationInfo; } /** * 重寫方法,清除當前用戶的的 受權緩存 * @param principals */ @Override public void clearCachedAuthorizationInfo(PrincipalCollection principals) { super.clearCachedAuthorizationInfo(principals); } /** * 重寫方法,清除當前用戶的 認證緩存 * @param principals */ @Override public void clearCachedAuthenticationInfo(PrincipalCollection principals) { super.clearCachedAuthenticationInfo(principals); } @Override public void clearCache(PrincipalCollection principals) { super.clearCache(principals); } /** * 自定義方法:清除全部 受權緩存 */ public void clearAllCachedAuthorizationInfo() { getAuthorizationCache().clear(); } /** * 自定義方法:清除全部 認證緩存 */ public void clearAllCachedAuthenticationInfo() { getAuthenticationCache().clear(); } /** * 自定義方法:清除全部的 認證緩存 和 受權緩存 */ public void clearAllCache() { clearAllCachedAuthenticationInfo(); clearAllCachedAuthorizationInfo(); } }