文章目录

  • 知识点介绍
  • 验证码过滤器
  • 登录账号控制过滤器
  • 自定义访问控制过滤器
  • 同步Sesion到数据库
  • 退出过滤器
  • 定期session验证任务调度器
  • shiro配置类
  • 自定义Session类(继承 SimpleSession)
  • 自定义SessionFactory会话(继承SessionFactory)
  • 自定义的ShiroSessionDao(继承EnterpriseCacheSessionDAO)

案例代码来自ruoyi管理系统的shiro部分

知识点介绍

AccessControlFilter

AccessControlFilter提供了访问控制的基础功能;比如是否允许访问/当访问拒绝时如何处理等:
isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false;

onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。
onPreHandle会自动调用这两个方法决定是否继续处理:

boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

配置过滤器的步骤:
1.继承AccessControlFilter
2.重写isAccessAllowed onAccessDenied(有时也需要重写onPreHandle等其他方法)
3.在shiroconfig中配置自己写的过滤器类,并加上@Bean注解,并配置过滤器链:

@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)shiroFilterFactoryBean.setSecurityManager(securityManager);// 身份认证失败,则跳转到登录页面的配置shiroFilterFactoryBean.setLoginUrl(loginUrl);// 权限认证失败,则跳转到指定页面shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);.......

(定时任务调度的使用):
4.配置会话管理器加入自定义的任务调度器

    /*** 会话管理器*/@Beanpublic OnlineWebSessionManager sessionManager(){OnlineWebSessionManager manager = new OnlineWebSessionManager();// 加入缓存管理器manager.setCacheManager(getEhCacheManager());// 删除过期的sessionmanager.setDeleteInvalidSessions(true);// 设置全局session超时时间manager.setGlobalSessionTimeout(expireTime * 60 * 1000);// 去掉 JSESSIONIDmanager.setSessionIdUrlRewritingEnabled(false);// 定义要使用的无效的Session定时调度器manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class));// 是否定时检查sessionmanager.setSessionValidationSchedulerEnabled(true);// 自定义SessionDaomanager.setSessionDAO(sessionDAO());// 自定义sessionFactorymanager.setSessionFactory(sessionFactory());return manager;}

验证码过滤器

public class CaptchaValidateFilter extends AccessControlFilter
{/*** 是否开启验证码*/private boolean captchaEnabled = true;/*** 验证码类型*/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;// 验证码禁用 或不是表单提交 允许访问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 : "");// 验证码清除,防止多次使用。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;}
}

登录账号控制过滤器

public class KickoutSessionFilter extends AccessControlFilter
{private final static ObjectMapper objectMapper = new ObjectMapper();/*** 同一个用户最大会话数**/private int maxSession = -1;/*** 踢出之前登录的/之后登录的用户 默认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){// 如果没有登录或用户最大会话数为-1,直接进行之后的流程return true;}try{Session session = subject.getSession();// 当前登录用户SysUser user = ShiroUtils.getSysUser();String loginName = user.getLoginName();Serializable sessionId = session.getId();// 读取缓存用户 没有就存入Deque<Serializable> deque = cache.get(loginName);if (deque == null){// 初始化队列deque = new ArrayDeque<Serializable>();}// 如果队列里没有此sessionId,且用户没有被踢出;放入队列if (!deque.contains(sessionId) && session.getAttribute("kickout") == null){// 将sessionId存入队列deque.push(sessionId);// 将用户的sessionId队列缓存cache.put(loginName, deque);}// 如果队列里的sessionId数超出最大会话数,开始踢人while (deque.size() > maxSession){// 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;Serializable kickoutSessionId = kickoutAfter ? deque.removeFirst() : deque.removeLast();// 踢出后再更新下缓存队列cache.put(loginName, deque);try{// 获取被踢出的sessionId的session对象Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));if (null != kickoutSession){// 设置会话的kickout属性表示踢出了kickoutSession.setAttribute("kickout", true);}}catch (Exception e){// 面对异常,我们选择忽略}}// 如果被踢出了,(前者或后者)直接退出,重定向到踢出后的地址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("您已在别处登录,请您修改密码或重新登录");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;}// 设置Cache的key的前缀public void setCacheManager(CacheManager cacheManager){// 必须和ehcache缓存配置中的缓存name一致this.cache = cacheManager.getCache(ShiroConstants.SYS_USERCACHE);}

自定义访问控制过滤器

public class OnlineSessionFilter extends AccessControlFilter
{/*** 强制退出后重定向的地址*/@Value("${shiro.user.loginUrl}")private String loginUrl;private OnlineSessionDAO onlineSessionDAO;/*** 表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回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对象设置进去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;}/*** 表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。*/@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception{Subject subject = getSubject(request, response);if (subject != null){subject.logout();}saveRequestAndRedirectToLogin(request, response);return false;}// 跳转到登录页@Overrideprotected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException{WebUtils.issueRedirect(request, response, loginUrl);}public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO){this.onlineSessionDAO = onlineSessionDAO;}
}

同步Sesion到数据库

public class SyncOnlineSessionFilter extends PathMatchingFilter
{//PathMatchingFilter提供了基于Ant风格的请求路径匹配功能及拦截器参数解析的功能,// 如“roles[admin,user]”自动根据“,”分割解析到一个路径参数配置并绑定到相应的路径:private OnlineSessionDAO onlineSessionDAO;/*** 同步会话数据到DB 一次请求最多同步一次 防止过多处理 需要放到Shiro过滤器之前*///onPreHandle:在preHandle中,当pathsMatch匹配一个路径后,// 会调用opPreHandler方法并将路径绑定参数配置传给mappedValue;// 然后可以在这个方法中进行一些验证(如角色授权),如果验证失败可以返回false中断流程;默认返回true;// 也就是说子类可以只实现onPreHandle即可,无须实现preHandle。如果没有path与请求路径匹配,默认是通过的(即preHandle返回true)。//@Overrideprotected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception{OnlineSession session = (OnlineSession) request.getAttribute(ShiroConstants.ONLINE_SESSION);// 如果session stop了 也不同步// session停止时间,如果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;}/*** 退出跳转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验证任务调度器

@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;/*** 定时器,用于处理超时的挂起请求,也用于连接断开时的重连。*/@Autowired@Qualifier("scheduledExecutorService")private ScheduledExecutorService executorService;private volatile boolean enabled = false;/*** 会话验证管理器*/@Autowired@Qualifier("sessionManager")@Lazyprivate ValidatingSessionManager sessionManager;// 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟@Value("${shiro.session.validationInterval}")private long sessionValidationInterval;@Overridepublic boolean isEnabled(){return this.enabled;}//设置定期验证session是否过期的时间间隔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();//对系统中所有打开/活动会话(那些尚未停止或过期的会话)进行会话验证,并验证每个会话。// 如果发现一个会话无效(例如它已经过期),它会被更新并保存到 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配置类

创建一个ShiroConfig,然后创建一个shiroFilter方法。在Shiro使用认证和授权时,其实都是通过ShiroFilterFactoryBean设置一些Shiro的拦截器进行的,拦截器会以LinkedHashMap的形式存储需要拦截的资源及链接,并且会按照顺序执行,其中键为拦截的资源或链接,值为拦截的形式(比如authc:所有URL都必须认证通过才可以访问,anon:所有URL都可以匿名访问),在拦截的过程中可以使用通配符,比如/**为拦截所有,所以一般/**放在最下面。同时,可以通过ShiroFilterFactoryBean设置登录链接、未授权链接、登录成功跳转页等,这里设置的shiroFilter方法内容如代码清单7-7所示。

@Configuration
public class ShiroConfig
{/*** Session超时时间,单位为毫秒(默认30分钟)*/@Value("${shiro.session.expireTime}")private int expireTime;/*** 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟*/@Value("${shiro.session.validationInterval}")private int validationInterval;/*** 同一个用户最大会话数*/@Value("${shiro.session.maxSession}")private int maxSession;/*** 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户*/@Value("${shiro.session.kickoutAfter}")private boolean kickoutAfter;/*** 验证码开关*/@Value("${shiro.user.captchaEnabled}")private boolean captchaEnabled;/*** 验证码类型*/@Value("${shiro.user.captchaType}")private String captchaType;/*** 设置Cookie的域名*/@Value("${shiro.cookie.domain}")private String domain;/*** 设置cookie的有效访问路径*/@Value("${shiro.cookie.path}")private String path;/*** 设置HttpOnly属性*/@Value("${shiro.cookie.httpOnly}")private boolean httpOnly;/*** 设置Cookie的过期时间,秒为单位*/@Value("${shiro.cookie.maxAge}")private int maxAge;/*** 设置cipherKey密钥*/@Value("${shiro.cookie.cipherKey}")private String cipherKey;/*** 登录地址*/@Value("${shiro.user.loginUrl}")private String loginUrl;/*** 权限认证失败地址*/@Value("${shiro.user.unauthorizedUrl}")private String unauthorizedUrl;/*** 是否开启记住我功能*/@Value("${shiro.rememberMe.enabled: false}")private boolean rememberMe;/*** 缓存管理器 使用Ehcache实现*/@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配置文件一直被占用,无法完全销毁项目重新部署*/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会话*/@Beanpublic OnlineSessionDAO sessionDAO(){OnlineSessionDAO sessionDAO = new OnlineSessionDAO();return sessionDAO;}/*** 自定义sessionFactory会话*/@Beanpublic OnlineSessionFactory sessionFactory(){OnlineSessionFactory sessionFactory = new OnlineSessionFactory();return sessionFactory;}/*** 会话管理器*/@Beanpublic OnlineWebSessionManager sessionManager(){OnlineWebSessionManager manager = new OnlineWebSessionManager();// 加入缓存管理器manager.setCacheManager(getEhCacheManager());// 删除过期的sessionmanager.setDeleteInvalidSessions(true);// 设置全局session超时时间manager.setGlobalSessionTimeout(expireTime * 60 * 1000);// 去掉 JSESSIONIDmanager.setSessionIdUrlRewritingEnabled(false);// 定义要使用的无效的Session定时调度器manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class));// 是否定时检查sessionmanager.setSessionValidationSchedulerEnabled(true);// 自定义SessionDaomanager.setSessionDAO(sessionDAO());// 自定义sessionFactorymanager.setSessionFactory(sessionFactory());return manager;}/*** 安全管理器*/@Beanpublic SecurityManager securityManager(UserRealm userRealm){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 设置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的核心安全接口,这个属性是必须的shiroFilterFactoryBean.setSecurityManager(securityManager);// 身份认证失败,则跳转到登录页面的配置shiroFilterFactoryBean.setLoginUrl(loginUrl);// 权限认证失败,则跳转到指定页面shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);// Shiro连接约束配置,即过滤链的定义LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();// 对静态资源设置匿名访问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");// 注册相关filterChainDefinitionMap.put("/register", "anon,captchaValidate");// 系统权限列表// 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());// 注销成功,则跳转到指定页面filters.put("logout", logoutFilter());shiroFilterFactoryBean.setFilters(filters);// 所有请求需要认证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;}/*** 自定义验证码过滤器*/public CaptchaValidateFilter captchaValidateFilter(){CaptchaValidateFilter captchaValidateFilter = new CaptchaValidateFilter();captchaValidateFilter.setCaptchaEnabled(captchaEnabled);captchaValidateFilter.setCaptchaType(captchaType);return captchaValidateFilter;}/*** cookie 属性设置*/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;}/*** 同一个用户多设备登录限制*/public KickoutSessionFilter kickoutSessionFilter(){KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter();kickoutSessionFilter.setCacheManager(getEhCacheManager());kickoutSessionFilter.setSessionManager(sessionManager());// 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录kickoutSessionFilter.setMaxSession(maxSession);// 是否踢出后来登录的,默认是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;/** 操作系统 */private String os;/** 在线状态 */private OnlineStatus status = OnlineStatus.on_line;/** 属性是否改变 优化session数据同步 */private transient boolean attributeChanged = false;@Overridepublic String getHost(){return host;}@Overridepublic void setHost(String host){this.host = host;}

自定义SessionFactory会话(继承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"));// 获取客户端操作系统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到数据库的周期 单位为毫秒(默认1分钟)*/@Value("${shiro.session.dbSyncPeriod}")private int dbSyncPeriod;/*** 上次同步数据库的时间戳*/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();}/*** 根据会话ID获取会话** @param sessionId 会话ID* @return ShiroSession*/@Overrideprotected Session doReadSession(Serializable sessionId){return sysShiroService.getSession(sessionId);}@Overridepublic void update(Session session) throws UnknownSessionException{super.update(session);}
//    EnterpriseCacheSessionDAO有一个方法:
//    protected Serializable doCreate(Session session) {//        /*通过sessionId保存对应的session*/
//        Serializable sessionId = this.generateSessionId(session);
//        /*将sessionId和session捆绑*/
//        this.assignSessionId(session, sessionId);
//        return sessionId;
//    }/*** 更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用*/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){// 时间差不足 无需同步needSync = false;}// isGuest = true 访客boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L;// session 数据变更了 同步if (!isGuest && onlineSession.isAttributeChanged()){needSync = true;}if (!needSync){return;}}// 更新上次同步数据库时间onlineSession.setAttribute(LAST_SYNC_DB_TIMESTAMP, onlineSession.getLastAccessTime());// 更新完后 重置标识if (onlineSession.isAttributeChanged()){onlineSession.resetAttributeChanged();}AsyncManager.me().execute(AsyncFactory.syncSessionToDb(onlineSession));}/*** 当会话过期/停止(如用户退出时)属性等会调用*/@Overrideprotected void doDelete(Session session){OnlineSession onlineSession = (OnlineSession) session;if (null == onlineSession){return;}onlineSession.setStatus(OnlineStatus.off_line);sysShiroService.deleteSession(onlineSession);}
}

springboot使用shiro配置多个过滤器和session同步案例相关推荐

  1. FILTER的使用与过滤器实现session登录案例

    步骤 1.分别创建一个实现HttpServlet和Filter的java类 2.实现Filter这个类之后会让继承三个方法,分别是 init:完成初始化功能     tomcat启动的时候执行一次 d ...

  2. 案例代码:springboot+shiro配置同一用户多设备登录最大会话数

    另一链接: springboot+springsecurity配置登录后踢出前一个登录用户 1.自定义登陆过滤器类: /*** 登录帐号控制过滤器* */ public class KickoutSe ...

  3. SpringBoot中如何配置使用过滤器(Filter)呢?

    转自: SpringBoot中如何配置使用过滤器(Filter)呢? 下文笔者讲述springboot中配置过滤器的方法分享,如下所示 实现思路:1.定义filter2.将filter注册进sprin ...

  4. SpringBoot Shiro 配置自定义密码加密器

    SpringBoot Shiro 配置自定义密码加密器 自定义认证加密方式 /*** 自定义认证加密方式*/ public static class CustomCredentialsMatcher ...

  5. springBoot配置多个过滤器filter的执行顺序

    1.自定义过滤器 过滤器类实现 javax.servlet.Filter.然后添加注解 @WebFilter(filterName="过滤器名称" urlPatterns=&quo ...

  6. SpringBoot整合Shiro安全框架完整实现

    目录 一.环境搭建 1. 导入shiro-spring依赖 2. 编写首页及其controller 3. 编写shiro配置类 二.Shiro实现登录拦截 1. 编写页面及其controller 2. ...

  7. SpringBoot 整合 Shiro 实现动态权限加载更新+ Session 共享 + 单点登录

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源: juejin.im/post/5d087d60518825 ...

  8. Springboot整合shiro基于url身份认证和授权认证

    你还不会shiro吗? 前奏 shiro核心配置文件(rolesFilter可选). 身份认证 多表登录源如何操作? 授权管理 如何解决界面多角色/资源问题 访问效果 权限管理在日常开发中很重要,所以 ...

  9. springboot整合shiro和session的详细过程和自定义登录拦截器

    文章目录 1.shiro依赖 2.shiro配置 shiro过滤器配置: 关联自定义的其他管理器 自定义会话工厂: 3.登陆时记录用户信息 4.shiro一些工具类的学习 5.自定义登录拦截器 shi ...

最新文章

  1. Dedecms5.7搜索结果页空白无内容的解决方法
  2. SpringBoot2.1.9 分布式锁ShedLock
  3. Unlicensed ARC session – terminating!
  4. python 3.5安装pywin32
  5. linux脚本是什么语言,Linux学习之Shell脚本语言的优势是什么?
  6. 为提升管理效率 蔚来汽车美国办公室裁员70名
  7. 数据结构上机实践第七周项目4 - 队列数组
  8. validationEngine中文版 — jquery强大的表单验证插件,留着以后会用得上
  9. 基于低代码平台实现物流行业的知识文档管理系统
  10. 网络与系统安全笔记------身份认证技术
  11. Spring学习总结01--Spring了解,IOC,DI
  12. 面向削峰填谷的电动汽车多目标优化调度策略——附代码
  13. B站哔哩哔哩:11 月 22 日上午九时正起恢复在香港联交所买卖
  14. Fifo中Underflow信号的含义
  15. 小技巧,IDEA 卡住不动解决办法
  16. ubuntu安装xcode包_Ubuntu12.04 安装 Mac OSX 10.9.5和 Xcode 6.2
  17. R.I.P,又一位程序员巨佬——左耳朵耗子陨落
  18. 2022年全球市场脱脂小麦胚芽粉总体规模、主要生产商、主要地区、产品和应用细分研究报告
  19. ESP-WRO0M-32(一):VS Code环境搭建
  20. 软件体系结构期末考试复习题(题中页码 与软件体系结构原理、方法与实践第2版 张友生编著 匹配)

热门文章

  1. linux传输文件无密码,Linux下scp无密码上传 下载 文件 目录
  2. pythonidea_idea集成python插件
  3. c语言平年表示方法,C语言平年闰年问题
  4. html5二次元插件,送你PS一键制作动漫二次元插件,80套背景+50种效果+20款中英字体...
  5. java解析字符串_用Java解析字符串有哪些不同的方法?
  6. Android SQLite保存多个选择题的选择信息
  7. 三十七、Sql 补充 | 数据库优化
  8. 三十一、Java多线程编程(下篇)
  9. java枚举可以int值不_java – 如何通过int值迭代枚举?
  10. 胶囊网络全新升级!引入自注意力机制的Efficient-CapsNet