springboot使用shiro配置多个过滤器和session同步案例
文章目錄
- 知識(shí)點(diǎn)介紹
- 驗(yàn)證碼過濾器
- 登錄賬號(hào)控制過濾器
- 自定義訪問控制過濾器
- 同步Sesion到數(shù)據(jù)庫(kù)
- 退出過濾器
- 定期session驗(yàn)證任務(wù)調(diào)度器
- shiro配置類
- 自定義Session類(繼承 SimpleSession)
- 自定義SessionFactory會(huì)話(繼承SessionFactory)
- 自定義的ShiroSessionDao(繼承EnterpriseCacheSessionDAO)
案例代碼來自ruoyi管理系統(tǒng)的shiro部分
知識(shí)點(diǎn)介紹
AccessControlFilter
AccessControlFilter提供了訪問控制的基礎(chǔ)功能;比如是否允許訪問/當(dāng)訪問拒絕時(shí)如何處理等:
isAccessAllowed:表示是否允許訪問;mappedValue就是[urls]配置中攔截器參數(shù)部分,如果允許訪問返回true,否則false;
onAccessDenied:表示當(dāng)訪問拒絕時(shí)是否已經(jīng)處理了;如果返回true表示需要繼續(xù)處理;如果返回false表示該攔截器實(shí)例已經(jīng)處理了,將直接返回即可。
onPreHandle會(huì)自動(dòng)調(diào)用這兩個(gè)方法決定是否繼續(xù)處理:
boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
配置過濾器的步驟:
1.繼承AccessControlFilter
2.重寫isAccessAllowed onAccessDenied(有時(shí)也需要重寫onPreHandle等其他方法)
3.在shiroconfig中配置自己寫的過濾器類,并加上@Bean注解,并配置過濾器鏈:
(定時(shí)任務(wù)調(diào)度的使用):
4.配置會(huì)話管理器加入自定義的任務(wù)調(diào)度器
驗(yàn)證碼過濾器
public class CaptchaValidateFilter extends AccessControlFilter {/*** 是否開啟驗(yàn)證碼*/private boolean captchaEnabled = true;/*** 驗(yàn)證碼類型*/private String captchaType = "math";public void setCaptchaEnabled(boolean captchaEnabled){this.captchaEnabled = captchaEnabled;}public void setCaptchaType(String captchaType){this.captchaType = captchaType;}@Overridepublic boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception{request.setAttribute(ShiroConstants.CURRENT_ENABLED, captchaEnabled);request.setAttribute(ShiroConstants.CURRENT_TYPE, captchaType);return super.onPreHandle(request, response, mappedValue);}@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)throws Exception{HttpServletRequest httpServletRequest = (HttpServletRequest) request;// 驗(yàn)證碼禁用 或不是表單提交 允許訪問if (captchaEnabled == false || !"post".equals(httpServletRequest.getMethod().toLowerCase())){return true;}return validateResponse(httpServletRequest, httpServletRequest.getParameter(ShiroConstants.CURRENT_VALIDATECODE));}public boolean validateResponse(HttpServletRequest request, String validateCode){Object obj = ShiroUtils.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);String code = String.valueOf(obj != null ? obj : "");// 驗(yàn)證碼清除,防止多次使用。request.getSession().removeAttribute(Constants.KAPTCHA_SESSION_KEY);if (StringUtils.isEmpty(validateCode) || !validateCode.equalsIgnoreCase(code)){return false;}return true;}@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception{request.setAttribute(ShiroConstants.CURRENT_CAPTCHA, ShiroConstants.CAPTCHA_ERROR);return true;} }登錄賬號(hào)控制過濾器
public class KickoutSessionFilter extends AccessControlFilter {private final static ObjectMapper objectMapper = new ObjectMapper();/*** 同一個(gè)用戶最大會(huì)話數(shù)**/private int maxSession = -1;/*** 踢出之前登錄的/之后登錄的用戶 默認(rèn)false踢出之前登錄的用戶**/private Boolean kickoutAfter = false;/*** 踢出后到的地址**/private String kickoutUrl;private SessionManager sessionManager;private Cache<String, Deque<Serializable>> cache;@Overrideprotected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o)throws Exception{return false;}@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception{Subject subject = getSubject(request, response);if (!subject.isAuthenticated() && !subject.isRemembered() || maxSession == -1){// 如果沒有登錄或用戶最大會(huì)話數(shù)為-1,直接進(jìn)行之后的流程return true;}try{Session session = subject.getSession();// 當(dāng)前登錄用戶SysUser user = ShiroUtils.getSysUser();String loginName = user.getLoginName();Serializable sessionId = session.getId();// 讀取緩存用戶 沒有就存入Deque<Serializable> deque = cache.get(loginName);if (deque == null){// 初始化隊(duì)列deque = new ArrayDeque<Serializable>();}// 如果隊(duì)列里沒有此sessionId,且用戶沒有被踢出;放入隊(duì)列if (!deque.contains(sessionId) && session.getAttribute("kickout") == null){// 將sessionId存入隊(duì)列deque.push(sessionId);// 將用戶的sessionId隊(duì)列緩存cache.put(loginName, deque);}// 如果隊(duì)列里的sessionId數(shù)超出最大會(huì)話數(shù),開始踢人while (deque.size() > maxSession){// 是否踢出后來登錄的,默認(rèn)是false;即后者登錄的用戶踢出前者登錄的用戶;Serializable kickoutSessionId = kickoutAfter ? deque.removeFirst() : deque.removeLast();// 踢出后再更新下緩存隊(duì)列cache.put(loginName, deque);try{// 獲取被踢出的sessionId的session對(duì)象Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));if (null != kickoutSession){// 設(shè)置會(huì)話的kickout屬性表示踢出了kickoutSession.setAttribute("kickout", true);}}catch (Exception e){// 面對(duì)異常,我們選擇忽略}}// 如果被踢出了,(前者或后者)直接退出,重定向到踢出后的地址if (session.getAttribute("kickout") != null && (Boolean) session.getAttribute("kickout") == true){// 退出登錄subject.logout();saveRequest(request);return isAjaxResponse(request, response);}return true;}catch (Exception e){return isAjaxResponse(request, response);}}private boolean isAjaxResponse(ServletRequest request, ServletResponse response) throws IOException{HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse res = (HttpServletResponse) response;if (ServletUtils.isAjaxRequest(req)){AjaxResult ajaxResult = AjaxResult.error("您已在別處登錄,請(qǐng)您修改密碼或重新登錄");ServletUtils.renderString(res, objectMapper.writeValueAsString(ajaxResult));}else{WebUtils.issueRedirect(request, response, kickoutUrl);}return false;}public void setMaxSession(int maxSession){this.maxSession = maxSession;}public void setKickoutAfter(boolean kickoutAfter){this.kickoutAfter = kickoutAfter;}public void setKickoutUrl(String kickoutUrl){this.kickoutUrl = kickoutUrl;}public void setSessionManager(SessionManager sessionManager){this.sessionManager = sessionManager;}// 設(shè)置Cache的key的前綴public void setCacheManager(CacheManager cacheManager){// 必須和ehcache緩存配置中的緩存name一致this.cache = cacheManager.getCache(ShiroConstants.SYS_USERCACHE);}自定義訪問控制過濾器
public class OnlineSessionFilter extends AccessControlFilter {/*** 強(qiáng)制退出后重定向的地址*/@Value("${shiro.user.loginUrl}")private String loginUrl;private OnlineSessionDAO onlineSessionDAO;/*** 表示是否允許訪問;mappedValue就是[urls]配置中攔截器參數(shù)部分,如果允許訪問返回true,否則false;*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)throws Exception{Subject subject = getSubject(request, response);if (subject == null || subject.getSession() == null){return true;}Session session = onlineSessionDAO.readSession(subject.getSession().getId());if (session != null && session instanceof OnlineSession){OnlineSession onlineSession = (OnlineSession) session;request.setAttribute(ShiroConstants.ONLINE_SESSION, onlineSession);// 把user對(duì)象設(shè)置進(jìn)去boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L;if (isGuest == true){SysUser user = ShiroUtils.getSysUser();if (user != null){onlineSession.setUserId(user.getUserId());onlineSession.setLoginName(user.getLoginName());onlineSession.setAvatar(user.getAvatar());onlineSession.setDeptName(user.getDept().getDeptName());onlineSession.markAttributeChanged();}}if (onlineSession.getStatus() == OnlineStatus.off_line){return false;}}return true;}/*** 表示當(dāng)訪問拒絕時(shí)是否已經(jīng)處理了;如果返回true表示需要繼續(xù)處理;如果返回false表示該攔截器實(shí)例已經(jīng)處理了,將直接返回即可。*/@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception{Subject subject = getSubject(request, response);if (subject != null){subject.logout();}saveRequestAndRedirectToLogin(request, response);return false;}// 跳轉(zhuǎn)到登錄頁(yè)@Overrideprotected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException{WebUtils.issueRedirect(request, response, loginUrl);}public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO){this.onlineSessionDAO = onlineSessionDAO;} }同步Sesion到數(shù)據(jù)庫(kù)
public class SyncOnlineSessionFilter extends PathMatchingFilter {//PathMatchingFilter提供了基于Ant風(fēng)格的請(qǐng)求路徑匹配功能及攔截器參數(shù)解析的功能,// 如“roles[admin,user]”自動(dòng)根據(jù)“,”分割解析到一個(gè)路徑參數(shù)配置并綁定到相應(yīng)的路徑:private OnlineSessionDAO onlineSessionDAO;/*** 同步會(huì)話數(shù)據(jù)到DB 一次請(qǐng)求最多同步一次 防止過多處理 需要放到Shiro過濾器之前*///onPreHandle:在preHandle中,當(dāng)pathsMatch匹配一個(gè)路徑后,// 會(huì)調(diào)用opPreHandler方法并將路徑綁定參數(shù)配置傳給mappedValue;// 然后可以在這個(gè)方法中進(jìn)行一些驗(yàn)證(如角色授權(quán)),如果驗(yàn)證失敗可以返回false中斷流程;默認(rèn)返回true;// 也就是說子類可以只實(shí)現(xiàn)onPreHandle即可,無須實(shí)現(xiàn)preHandle。如果沒有path與請(qǐng)求路徑匹配,默認(rèn)是通過的(即preHandle返回true)。//@Overrideprotected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception{OnlineSession session = (OnlineSession) request.getAttribute(ShiroConstants.ONLINE_SESSION);// 如果session stop了 也不同步// session停止時(shí)間,如果stopTimestamp不為null,則代表已停止if (session != null && session.getUserId() != null && session.getStopTimestamp() == null){onlineSessionDAO.syncToDb(session);}return true;}public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO){this.onlineSessionDAO = onlineSessionDAO;} }退出過濾器
public class LogoutFilter extends org.apache.shiro.web.filter.authc.LogoutFilter {private static final Logger log = LoggerFactory.getLogger(LogoutFilter.class);/*** 退出后重定向的地址*/private String loginUrl;public String getLoginUrl(){return loginUrl;}public void setLoginUrl(String loginUrl){this.loginUrl = loginUrl;}@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception{try{Subject subject = getSubject(request, response);String redirectUrl = getRedirectUrl(request, response, subject);try{SysUser user = ShiroUtils.getSysUser();if (StringUtils.isNotNull(user)){String loginName = user.getLoginName();// 記錄用戶退出日志AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));// 清理緩存SpringUtils.getBean(ISysUserOnlineService.class).removeUserCache(loginName, ShiroUtils.getSessionId());}// 退出登錄subject.logout();}catch (SessionException ise){log.error("logout fail.", ise);}issueRedirect(request, response, redirectUrl);}catch (Exception e){log.error("Encountered session exception during logout. This can generally safely be ignored.", e);}return false;}/*** 退出跳轉(zhuǎn)URL*/@Overrideprotected String getRedirectUrl(ServletRequest request, ServletResponse response, Subject subject){String url = getLoginUrl();if (StringUtils.isNotEmpty(url)){return url;}return super.getRedirectUrl(request, response, subject);} }定期session驗(yàn)證任務(wù)調(diào)度器
@Component public class SpringSessionValidationScheduler implements SessionValidationScheduler {private static final Logger log = LoggerFactory.getLogger(SpringSessionValidationScheduler.class);public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;/*** 定時(shí)器,用于處理超時(shí)的掛起請(qǐng)求,也用于連接斷開時(shí)的重連。*/@Autowired@Qualifier("scheduledExecutorService")private ScheduledExecutorService executorService;private volatile boolean enabled = false;/*** 會(huì)話驗(yàn)證管理器*/@Autowired@Qualifier("sessionManager")@Lazyprivate ValidatingSessionManager sessionManager;// 相隔多久檢查一次session的有效性,單位毫秒,默認(rèn)就是10分鐘@Value("${shiro.session.validationInterval}")private long sessionValidationInterval;@Overridepublic boolean isEnabled(){return this.enabled;}//設(shè)置定期驗(yàn)證session是否過期的時(shí)間間隔public void setSessionValidationInterval(long sessionValidationInterval){this.sessionValidationInterval = sessionValidationInterval;}/*** Starts session validation by creating a spring PeriodicTrigger.*/@Overridepublic void enableSessionValidation(){enabled = true;if (log.isDebugEnabled()){log.debug("Scheduling session validation job using Spring Scheduler with "+ "session validation interval of [" + sessionValidationInterval + "]ms...");}try{executorService.scheduleAtFixedRate(new Runnable(){@Overridepublic void run(){if (enabled){sessionManager.validateSessions();//org.apache.shiro.session.mgt包下的方法validateSessions();//對(duì)系統(tǒng)中所有打開/活動(dòng)會(huì)話(那些尚未停止或過期的會(huì)話)進(jìn)行會(huì)話驗(yàn)證,并驗(yàn)證每個(gè)會(huì)話。// 如果發(fā)現(xiàn)一個(gè)會(huì)話無效(例如它已經(jīng)過期),它會(huì)被更新并保存到 EIS。}}}, 1000, sessionValidationInterval * 60 * 1000, TimeUnit.MILLISECONDS);this.enabled = true;if (log.isDebugEnabled()){log.debug("Session validation job successfully scheduled with Spring Scheduler.");}}catch (Exception e){if (log.isErrorEnabled()){log.error("Error starting the Spring Scheduler session validation job. Session validation may not occur.", e);}}}@Overridepublic void disableSessionValidation(){if (log.isDebugEnabled()){log.debug("Stopping Spring Scheduler session validation job...");}if (this.enabled){Threads.shutdownAndAwaitTermination(executorService);}this.enabled = false;} }shiro配置類
創(chuàng)建一個(gè)ShiroConfig,然后創(chuàng)建一個(gè)shiroFilter方法。在Shiro使用認(rèn)證和授權(quán)時(shí),其實(shí)都是通過ShiroFilterFactoryBean設(shè)置一些Shiro的攔截器進(jìn)行的,攔截器會(huì)以LinkedHashMap的形式存儲(chǔ)需要攔截的資源及鏈接,并且會(huì)按照順序執(zhí)行,其中鍵為攔截的資源或鏈接,值為攔截的形式(比如authc:所有URL都必須認(rèn)證通過才可以訪問,anon:所有URL都可以匿名訪問),在攔截的過程中可以使用通配符,比如/**為攔截所有,所以一般/**放在最下面。同時(shí),可以通過ShiroFilterFactoryBean設(shè)置登錄鏈接、未授權(quán)鏈接、登錄成功跳轉(zhuǎn)頁(yè)等,這里設(shè)置的shiroFilter方法內(nèi)容如代碼清單7-7所示。
@Configuration public class ShiroConfig {/*** Session超時(shí)時(shí)間,單位為毫秒(默認(rèn)30分鐘)*/@Value("${shiro.session.expireTime}")private int expireTime;/*** 相隔多久檢查一次session的有效性,單位毫秒,默認(rèn)就是10分鐘*/@Value("${shiro.session.validationInterval}")private int validationInterval;/*** 同一個(gè)用戶最大會(huì)話數(shù)*/@Value("${shiro.session.maxSession}")private int maxSession;/*** 踢出之前登錄的/之后登錄的用戶,默認(rèn)踢出之前登錄的用戶*/@Value("${shiro.session.kickoutAfter}")private boolean kickoutAfter;/*** 驗(yàn)證碼開關(guān)*/@Value("${shiro.user.captchaEnabled}")private boolean captchaEnabled;/*** 驗(yàn)證碼類型*/@Value("${shiro.user.captchaType}")private String captchaType;/*** 設(shè)置Cookie的域名*/@Value("${shiro.cookie.domain}")private String domain;/*** 設(shè)置cookie的有效訪問路徑*/@Value("${shiro.cookie.path}")private String path;/*** 設(shè)置HttpOnly屬性*/@Value("${shiro.cookie.httpOnly}")private boolean httpOnly;/*** 設(shè)置Cookie的過期時(shí)間,秒為單位*/@Value("${shiro.cookie.maxAge}")private int maxAge;/*** 設(shè)置cipherKey密鑰*/@Value("${shiro.cookie.cipherKey}")private String cipherKey;/*** 登錄地址*/@Value("${shiro.user.loginUrl}")private String loginUrl;/*** 權(quán)限認(rèn)證失敗地址*/@Value("${shiro.user.unauthorizedUrl}")private String unauthorizedUrl;/*** 是否開啟記住我功能*/@Value("${shiro.rememberMe.enabled: false}")private boolean rememberMe;/*** 緩存管理器 使用Ehcache實(shí)現(xiàn)*/@Beanpublic EhCacheManager getEhCacheManager(){net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("ruoyi");EhCacheManager em = new EhCacheManager();if (StringUtils.isNull(cacheManager)){em.setCacheManager(new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream()));return em;}else{em.setCacheManager(cacheManager);return em;}}/*** 返回配置文件流 避免ehcache配置文件一直被占用,無法完全銷毀項(xiàng)目重新部署*/protected InputStream getCacheManagerConfigFileInputStream(){String configFile = "classpath:ehcache/ehcache-shiro.xml";InputStream inputStream = null;try{inputStream = ResourceUtils.getInputStreamForPath(configFile);byte[] b = IOUtils.toByteArray(inputStream);InputStream in = new ByteArrayInputStream(b);return in;}catch (IOException e){throw new ConfigurationException("Unable to obtain input stream for cacheManagerConfigFile [" + configFile + "]", e);}finally{IOUtils.closeQuietly(inputStream);}}/*** 自定義Realm*/@Beanpublic UserRealm userRealm(EhCacheManager cacheManager){UserRealm userRealm = new UserRealm();userRealm.setAuthorizationCacheName(Constants.SYS_AUTH_CACHE);userRealm.setCacheManager(cacheManager);return userRealm;}/*** 自定義sessionDAO會(huì)話*/@Beanpublic OnlineSessionDAO sessionDAO(){OnlineSessionDAO sessionDAO = new OnlineSessionDAO();return sessionDAO;}/*** 自定義sessionFactory會(huì)話*/@Beanpublic OnlineSessionFactory sessionFactory(){OnlineSessionFactory sessionFactory = new OnlineSessionFactory();return sessionFactory;}/*** 會(huì)話管理器*/@Beanpublic OnlineWebSessionManager sessionManager(){OnlineWebSessionManager manager = new OnlineWebSessionManager();// 加入緩存管理器manager.setCacheManager(getEhCacheManager());// 刪除過期的sessionmanager.setDeleteInvalidSessions(true);// 設(shè)置全局session超時(shí)時(shí)間manager.setGlobalSessionTimeout(expireTime * 60 * 1000);// 去掉 JSESSIONIDmanager.setSessionIdUrlRewritingEnabled(false);// 定義要使用的無效的Session定時(shí)調(diào)度器manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class));// 是否定時(shí)檢查sessionmanager.setSessionValidationSchedulerEnabled(true);// 自定義SessionDaomanager.setSessionDAO(sessionDAO());// 自定義sessionFactorymanager.setSessionFactory(sessionFactory());return manager;}/*** 安全管理器*/@Beanpublic SecurityManager securityManager(UserRealm userRealm){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 設(shè)置realm.securityManager.setRealm(userRealm);// 記住我securityManager.setRememberMeManager(rememberMe ? rememberMeManager() : null);// 注入緩存管理器;securityManager.setCacheManager(getEhCacheManager());// session管理器securityManager.setSessionManager(sessionManager());return securityManager;}/*** 退出過濾器*/public LogoutFilter logoutFilter(){LogoutFilter logoutFilter = new LogoutFilter();logoutFilter.setLoginUrl(loginUrl);return logoutFilter;}/*** Shiro過濾器配置*/@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// Shiro的核心安全接口,這個(gè)屬性是必須的shiroFilterFactoryBean.setSecurityManager(securityManager);// 身份認(rèn)證失敗,則跳轉(zhuǎn)到登錄頁(yè)面的配置shiroFilterFactoryBean.setLoginUrl(loginUrl);// 權(quán)限認(rèn)證失敗,則跳轉(zhuǎn)到指定頁(yè)面shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);// Shiro連接約束配置,即過濾鏈的定義LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();// 對(duì)靜態(tài)資源設(shè)置匿名訪問filterChainDefinitionMap.put("/favicon.ico**", "anon");filterChainDefinitionMap.put("/ruoyi.png**", "anon");filterChainDefinitionMap.put("/html/**", "anon");filterChainDefinitionMap.put("/css/**", "anon");filterChainDefinitionMap.put("/docs/**", "anon");filterChainDefinitionMap.put("/fonts/**", "anon");filterChainDefinitionMap.put("/img/**", "anon");filterChainDefinitionMap.put("/ajax/**", "anon");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/ruoyi/**", "anon");filterChainDefinitionMap.put("/captcha/captchaImage**", "anon");// 退出 logout地址,shiro去清除sessionfilterChainDefinitionMap.put("/logout", "logout");// 不需要攔截的訪問filterChainDefinitionMap.put("/login", "anon,captchaValidate");// 注冊(cè)相關(guān)filterChainDefinitionMap.put("/register", "anon,captchaValidate");// 系統(tǒng)權(quán)限列表// filterChainDefinitionMap.putAll(SpringUtils.getBean(IMenuService.class).selectPermsAll());Map<String, Filter> filters = new LinkedHashMap<String, Filter>();filters.put("onlineSession", onlineSessionFilter());filters.put("syncOnlineSession", syncOnlineSessionFilter());filters.put("captchaValidate", captchaValidateFilter());filters.put("kickout", kickoutSessionFilter());// 注銷成功,則跳轉(zhuǎn)到指定頁(yè)面filters.put("logout", logoutFilter());shiroFilterFactoryBean.setFilters(filters);// 所有請(qǐng)求需要認(rèn)證filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}/*** 自定義在線用戶處理過濾器*/public OnlineSessionFilter onlineSessionFilter(){OnlineSessionFilter onlineSessionFilter = new OnlineSessionFilter();onlineSessionFilter.setLoginUrl(loginUrl);onlineSessionFilter.setOnlineSessionDAO(sessionDAO());return onlineSessionFilter;}/*** 自定義在線用戶同步過濾器*/public SyncOnlineSessionFilter syncOnlineSessionFilter(){SyncOnlineSessionFilter syncOnlineSessionFilter = new SyncOnlineSessionFilter();syncOnlineSessionFilter.setOnlineSessionDAO(sessionDAO());return syncOnlineSessionFilter;}/*** 自定義驗(yàn)證碼過濾器*/public CaptchaValidateFilter captchaValidateFilter(){CaptchaValidateFilter captchaValidateFilter = new CaptchaValidateFilter();captchaValidateFilter.setCaptchaEnabled(captchaEnabled);captchaValidateFilter.setCaptchaType(captchaType);return captchaValidateFilter;}/*** cookie 屬性設(shè)置*/public SimpleCookie rememberMeCookie(){SimpleCookie cookie = new SimpleCookie("rememberMe");cookie.setDomain(domain);cookie.setPath(path);cookie.setHttpOnly(httpOnly);cookie.setMaxAge(maxAge * 24 * 60 * 60);return cookie;}/*** 記住我*/public CookieRememberMeManager rememberMeManager(){CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());if (StringUtils.isNotEmpty(cipherKey)){cookieRememberMeManager.setCipherKey(Base64.decode(cipherKey));}else{cookieRememberMeManager.setCipherKey(CipherUtils.generateNewKey(128, "AES").getEncoded());}return cookieRememberMeManager;}/*** 同一個(gè)用戶多設(shè)備登錄限制*/public KickoutSessionFilter kickoutSessionFilter(){KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter();kickoutSessionFilter.setCacheManager(getEhCacheManager());kickoutSessionFilter.setSessionManager(sessionManager());// 同一個(gè)用戶最大的會(huì)話數(shù),默認(rèn)-1無限制;比如2的意思是同一個(gè)用戶允許最多同時(shí)兩個(gè)人登錄kickoutSessionFilter.setMaxSession(maxSession);// 是否踢出后來登錄的,默認(rèn)是false;即后者登錄的用戶踢出前者登錄的用戶;踢出順序kickoutSessionFilter.setKickoutAfter(kickoutAfter);// 被踢出后重定向到的地址;kickoutSessionFilter.setKickoutUrl("/login?kickout=1");return kickoutSessionFilter;}/*** thymeleaf模板引擎和shiro框架的整合*/@Beanpublic ShiroDialect shiroDialect(){return new ShiroDialect();}/*** 開啟Shiro注解通知器*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;} }自定義Session類(繼承 SimpleSession)
public class OnlineSession extends SimpleSession {private static final long serialVersionUID = 1L;/** 用戶ID */private Long userId;/** 用戶名稱 */private String loginName;/** 部門名稱 */private String deptName;/** 用戶頭像 */private String avatar;/** 登錄IP地址 */private String host;/** 瀏覽器類型 */private String browser;/** 操作系統(tǒng) */private String os;/** 在線狀態(tài) */private OnlineStatus status = OnlineStatus.on_line;/** 屬性是否改變 優(yōu)化session數(shù)據(jù)同步 */private transient boolean attributeChanged = false;@Overridepublic String getHost(){return host;}@Overridepublic void setHost(String host){this.host = host;}自定義SessionFactory會(huì)話(繼承SessionFactory)
@Data @Component public class OnlineSessionFactory implements SessionFactory {@Overridepublic Session createSession(SessionContext initData){OnlineSession session = new OnlineSession();if (initData != null && initData instanceof WebSessionContext){WebSessionContext sessionContext = (WebSessionContext) initData;HttpServletRequest request = (HttpServletRequest) sessionContext.getServletRequest();if (request != null){UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));// 獲取客戶端操作系統(tǒng)String os = userAgent.getOperatingSystem().getName();// 獲取客戶端瀏覽器String browser = userAgent.getBrowser().getName();session.setHost(IpUtils.getIpAddr(request));session.setBrowser(browser);session.setOs(os);}}return session;} }自定義的ShiroSessionDao(繼承EnterpriseCacheSessionDAO)
public class OnlineSessionDAO extends EnterpriseCacheSessionDAO {//extends AbstractSessionDAO/*** 同步session到數(shù)據(jù)庫(kù)的周期 單位為毫秒(默認(rèn)1分鐘)*/@Value("${shiro.session.dbSyncPeriod}")private int dbSyncPeriod;/*** 上次同步數(shù)據(jù)庫(kù)的時(shí)間戳*/private static final String LAST_SYNC_DB_TIMESTAMP = OnlineSessionDAO.class.getName() + "LAST_SYNC_DB_TIMESTAMP";@Autowiredprivate SysShiroService sysShiroService;public OnlineSessionDAO(){super();}public OnlineSessionDAO(long expireTime){super();}/*** 根據(jù)會(huì)話ID獲取會(huì)話** @param sessionId 會(huì)話ID* @return ShiroSession*/@Overrideprotected Session doReadSession(Serializable sessionId){return sysShiroService.getSession(sessionId);}@Overridepublic void update(Session session) throws UnknownSessionException{super.update(session);} // EnterpriseCacheSessionDAO有一個(gè)方法: // protected Serializable doCreate(Session session) { // /*通過sessionId保存對(duì)應(yīng)的session*/ // Serializable sessionId = this.generateSessionId(session); // /*將sessionId和session捆綁*/ // this.assignSessionId(session, sessionId); // return sessionId; // }/*** 更新會(huì)話;如更新會(huì)話最后訪問時(shí)間/停止會(huì)話/設(shè)置超時(shí)時(shí)間/設(shè)置移除屬性等會(huì)調(diào)用*/public void syncToDb(OnlineSession onlineSession){Date lastSyncTimestamp = (Date) onlineSession.getAttribute(LAST_SYNC_DB_TIMESTAMP);if (lastSyncTimestamp != null){boolean needSync = true;long deltaTime = onlineSession.getLastAccessTime().getTime() - lastSyncTimestamp.getTime();if (deltaTime < dbSyncPeriod * 60 * 1000){// 時(shí)間差不足 無需同步needSync = false;}// isGuest = true 訪客boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L;// session 數(shù)據(jù)變更了 同步if (!isGuest && onlineSession.isAttributeChanged()){needSync = true;}if (!needSync){return;}}// 更新上次同步數(shù)據(jù)庫(kù)時(shí)間onlineSession.setAttribute(LAST_SYNC_DB_TIMESTAMP, onlineSession.getLastAccessTime());// 更新完后 重置標(biāo)識(shí)if (onlineSession.isAttributeChanged()){onlineSession.resetAttributeChanged();}AsyncManager.me().execute(AsyncFactory.syncSessionToDb(onlineSession));}/*** 當(dāng)會(huì)話過期/停止(如用戶退出時(shí))屬性等會(huì)調(diào)用*/@Overrideprotected void doDelete(Session session){OnlineSession onlineSession = (OnlineSession) session;if (null == onlineSession){return;}onlineSession.setStatus(OnlineStatus.off_line);sysShiroService.deleteSession(onlineSession);} }總結(jié)
以上是生活随笔為你收集整理的springboot使用shiro配置多个过滤器和session同步案例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【学习笔记】springboot的Aut
- 下一篇: springboot封装统一查询对象进行