spring boot集成shiro
步骤
- 修改
pom.xml引入shiro需要的jar包<dependencies> …… <!--开启 cache 缓存--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- ehcache 缓存 --> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.0</version> </dependency> …… </dependencies> - 增加shiro的配置类
ShiroConfiguration.javapackage top.z_f.simpleerp.config.shiro; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.filter.authc.LogoutFilter; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.Cookie; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import java.util.LinkedHashMap; import java.util.Map; import javax.servlet.Filter; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.mgt.RememberMeManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.mgt.SessionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import top.z_f.simpleerp.utils.sys.AppCode; import top.z_f.simpleerp.utils.sys.CacheUtils; import top.z_f.simpleerp.utils.sys.IdGenUtils; /** * @author zhangzhen * */ @Configuration public class ShiroConfiguration { private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class); /** * Shiro Bean生命周期管理 * @return */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { logger.info("ShiroConfiguration.lifecycleBeanPostProcessor()"); return new LifecycleBeanPostProcessor(); } @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { logger.info("ShiroConfiguration >> shiroFilter :: shiro过滤器配置开始"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 设置安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); // 默认跳转到登录页面 shiroFilterFactoryBean.setLoginUrl("/z/login"); // 登录成功后的页面 shiroFilterFactoryBean.setSuccessUrl("/z"); // 权限验证失败后跳转页面 shiroFilterFactoryBean.setUnauthorizedUrl("/403"); // 拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 配置不会被拦截的连接,顺序判断 filterChainDefinitionMap.put("/static/**", "anon"); // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/z/logout", "logout"); //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/z/login", "authc"); // 登录页面需要验证 filterChainDefinitionMap.put("/**", "user"); // rememberMe,随着开发的深入自己修改 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); // 重写登录校验 Map<String, Filter> filters = shiroFilterFactoryBean.getFilters(); filters.put("authc", formAuthenticationFilter()); filters.put("logout", new LogoutFilter()); shiroFilterFactoryBean.setFilters(filters); logger.info("ShiroConfiguration >> shiroFilter :: shiro过滤器配置结束"); return shiroFilterFactoryBean; } /** * 核心的安全事务管理器 * @return */ @Bean public SecurityManager securityManager() { logger.info("ShiroConfiguration.securityManager()"); DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(managerUserShiroRealm()); // 设置security缓存为ehcache securityManager.setCacheManager(ehCacheManager()); // // 设置rememberMe // securityManager.setRememberMeManager(rememberMeManager()); // 设置session securityManager.setSessionManager(sessionManager()); return securityManager; } @Bean public FormAuthenticationFilter formAuthenticationFilter() { logger.info("ShiroConfiguration.formAuthenticationFilter()"); return new FormAuthenticationFilter(); } /** * 身份认证Realm,此处的注入不可以缺少。否则会在UserShiroRealm中注入对象会报空指针. * @return */ @Bean public ManagerUserShiroRealm managerUserShiroRealm() { logger.info("ShiroConfiguration.managerUserShiroRealm()"); ManagerUserShiroRealm managerUserShiroRealm = new ManagerUserShiroRealm(); managerUserShiroRealm.setName("SystemRealm"); // managerUserShiroRealm.setAuthorizationCachingEnabled(true); managerUserShiroRealm.setAuthorizationCacheName(CacheUtils.AUTHORIZATION_CACHE); // managerUserShiroRealm.setAuthenticationCachingEnabled(true); managerUserShiroRealm.setAuthenticationCacheName(CacheUtils.AUTHENTICATION_CACHE); return managerUserShiroRealm; } /** * 配置缓存 * @return */ @Bean public EhCacheManager ehCacheManager() { logger.info("ShiroConfiguration.ehCacheManager()"); EhCacheManager em = new EhCacheManager(); em.setCacheManagerConfigFile("classpath:ehcache.xml"); return em; } // // 配置RememberMe 开始(RememberMe不需要单独配置,在session中已经有对它的管理,只需要在登录的时候实现就可以了FormAuthenticationFilter.createToken) // /** // * rememberMe实际配置 // * @return // */ // @Bean // public RememberMeManager rememberMeManager() { // logger.info("ShiroConfiguration.rememberMeManager()"); // CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); // cookieRememberMeManager.setCookie(cookie()); // return cookieRememberMeManager; // } // // /** // * RememberMe需要缓存配合 // * @return // */ // @Bean // public Cookie cookie() { // logger.info("ShiroConfiguration.cookie()"); // SimpleCookie simpleCookie = new SimpleCookie(); // simpleCookie.setName("rememberMeCookie"); // // 缓存失效时间,单位:s // simpleCookie.setMaxAge(AppCode.cookieTimeOut()); //// // 防止客户端获取cookie值,防止XSS攻击 //// simpleCookie.setHttpOnly(true); // return simpleCookie; // } // // 配置RememberMe 结束 // session 开始 /** * Session管理 * @return */ @Bean public SessionManager sessionManager() { DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager(); defaultWebSessionManager.setSessionDAO(sessionDAO()); // 会话超时时间(有效期),单位:毫秒;会话超时, 单位:毫秒, 20m=1200000ms, 30m=1800000ms, 60m=3600000ms defaultWebSessionManager.setGlobalSessionTimeout(AppCode.globalSessionTimeout()); // 检查会话有效期间隔(即相隔多久检查一次session的有效性),定时清理失效会话, 清理用户直接关闭浏览器造成的孤立会话;会话清理间隔时间, 单位:毫秒,2m=120000ms defaultWebSessionManager.setSessionValidationInterval(AppCode.sessionValidationInterval()); // 是否开启扫描 defaultWebSessionManager.setSessionValidationSchedulerEnabled(true); // 设置对应cookie defaultWebSessionManager.setSessionIdCookie(simpleCookie()); // 去掉URL中的JSESSIONID defaultWebSessionManager.setSessionIdUrlRewritingEnabled(false); return defaultWebSessionManager; } /** * SessionDAO * @return */ @Bean public CacheSessionDAO sessionDAO() { CacheSessionDAO sessionDAO = new CacheSessionDAO(); // sessionID设置 sessionDAO.setSessionIdGenerator(new IdGenUtils()); // cache名称 sessionDAO.setActiveSessionsCacheName(CacheUtils.ACTIVE_SESSIONS_CACHE); return sessionDAO; } @Bean public SimpleCookie simpleCookie() { SimpleCookie simpleCookie = new SimpleCookie(); simpleCookie.setName("zf.session.id"); // // cookie失效时间,单位:s // simpleCookie.setMaxAge(AppCode.cookieTimeOut()); // 防止客户端获取cookie值,防止XSS攻击 simpleCookie.setHttpOnly(true); return simpleCookie; } // session 结束 // 开启shiro注解;增加后可以在代码中写shiro的注解 开始 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator app=new DefaultAdvisorAutoProxyCreator(); app.setProxyTargetClass(true); return app; } // 开启shiro注解 结束 } - 自定义Realm类
package top.z_f.simpleerp.config.shiro; import java.util.List; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.DisabledAccountException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import top.z_f.simpleerp.entity.SysUser; import top.z_f.simpleerp.entity.SysUserRoleKey; import top.z_f.simpleerp.info.LoginUserInfo; import top.z_f.simpleerp.service.SysUserRoleService; import top.z_f.simpleerp.service.SysUserService; /** * * 声明用于管理台用户验证的自定义Realm类 * @author zhangzhen * */ public class ManagerUserShiroRealm extends AuthorizingRealm { /** * logger记录 */ private static final Logger logger = LoggerFactory.getLogger(ManagerUserShiroRealm.class); @Autowired @Lazy SysUserService sysUserService; @Autowired @Lazy SysUserRoleService sysUserRoleService; /** * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("UserShiroRealm >> doGetAuthenticationInfo :: 授权验证"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); SysUser userInfo = (SysUser)principalCollection.getPrimaryPrincipal(); List<SysUserRoleKey> roleList = sysUserRoleService.findRoleByUserId(userInfo.getId()); for(SysUserRoleKey userRole : roleList){ authorizationInfo.addRole(userRole.getSysRoleid()); } return authorizationInfo; } /** * 认证信息.(身份验证) : Authentication 是用来验证用户身份 * 提供帐户信息,登录时调用,返回认证信息 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info("UserShiroRealm >> doGetAuthenticationInfo :: 登录验证"); // 获取用户输入的账号 String mobileOrEmail = (String)authenticationToken.getPrincipal(); SysUser sysUserFind = new SysUser(); sysUserFind.setMobile(mobileOrEmail); sysUserFind.setEmail(mobileOrEmail); // 通过email/mobile从数据库中查找 ,如果找不到抛出异常 // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 SysUser userInfo = sysUserService.findUserByMobileOrEmail(sysUserFind); System.out.println("userInfo ----->> " + userInfo); if(userInfo == null || userInfo.getStatus() == 20){ // 用户不存在(或者已经逻辑删除)就抛出异常 throw new UnknownAccountException("msg:用户不存在"); } else if (userInfo.getStatus() == 30) { // 用户停用/离职 throw new DisabledAccountException("msg:该用户已离职,请用其他帐户登录"); } else if (userInfo.getStatus() == 40) { // 冻结;账号异常,冻结账号禁止登录 throw new LockedAccountException("msg:该帐号已被冻结"); } LoginUserInfo loginUserInfo = new LoginUserInfo(); BeanUtils.copyProperties(userInfo, loginUserInfo); // // 在这里可以获得session吗?可以 // Session session = UserShiroUtils.getSession(); // logger.info("session.getId() ==" + session.getId()); // //此处是获取数据库内的账号、密码、盐值,保存到登陆信息info中 // SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( // userInfo.getMobile(), // userInfo.getPassword(), //// ByteSource.Util.bytes(userInfo.getSalt()), // null, // getName() //realm name // ); // 此处是获取数据库内的账号、密码、盐值,保存到登陆信息info中 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(loginUserInfo, userInfo.getPassword(), getName() //realm name ); return authenticationInfo; } } - 自定义表单登录验证
package top.z_f.simpleerp.config.shiro; import java.util.Date; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import top.z_f.simpleerp.entity.SysUser; import top.z_f.simpleerp.info.LoginUserInfo; import top.z_f.simpleerp.service.SysUserService; import top.z_f.simpleerp.utils.common.ZfStringUtils; import top.z_f.simpleerp.utils.sys.UserShiroUtils; /** * 实现shiro登录验证 * @author zhangzhen * @date 2019-06-10 */ public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter { private static final Logger logger = LoggerFactory.getLogger(FormAuthenticationFilter.class); public static final String DEFAULT_MESSAGE_PARAM = "message"; private String messageParam = DEFAULT_MESSAGE_PARAM; @Autowired SysUserService sysUserService; @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { logger.info("FormAuthenticationFilter.createToken()"); String username = getUsername(request); String password = getPassword(request); if (password==null){ password = ""; } boolean rememberMe = isRememberMe(request); String host = ZfStringUtils.getRemoteAddr((HttpServletRequest)request); return new UsernamePasswordToken(username, password.toCharArray(), rememberMe, host); } /** * 登录成功之后跳转URL */ public String getSuccessUrl() { return super.getSuccessUrl(); } public String getMessageParam() { return messageParam; } public void setMessageParam(String messageParam) { this.messageParam = messageParam; } @Override protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception { WebUtils.issueRedirect(request, response, getSuccessUrl(), null, true); } /** * 登录成功调用事件 * RememberMe不会走这个流程 */ @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { // issueSuccessRedirect(request, response); logger.info("UserShiroUtils.getSession().getId() >" + UserShiroUtils.getSession().getId());; // 登录成功的时候,更新用户最后登录时间 LoginUserInfo loginUserInfo = (LoginUserInfo) subject.getPrincipal(); // 为了防止其他数据发生变化,更改为根据id获取user信息,然后进行更新 SysUser sysUser = sysUserService.selectByPrimaryKey(loginUserInfo.getId()); // 最后登录时间 sysUser.setLastLoginDate(new Date()); // 最后登录ip sysUser.setLastLoginIp(ZfStringUtils.getIpAddress((HttpServletRequest) request)); sysUserService.updateByPrimaryKey(sysUser); // TODO 并将用户session信息存如缓存 logger.info("UserShiroUtils.getSession().getId() >" + UserShiroUtils.getSession().getId());; return super.onLoginSuccess(token, subject, request, response); } /** * 登录失败调用事件 */ @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { String className = e.getClass().getName(), message = ""; // if (IncorrectCredentialsException.class.getName().equals(className) // || UnknownAccountException.class.getName().equals(className)){ if (IncorrectCredentialsException.class.getName().equals(className)){ message = "密码错误"; } else if (e.getMessage() != null && StringUtils.startsWith(e.getMessage(), "msg:")){ message = StringUtils.replace(e.getMessage(), "msg:", ""); } else{ // message = "系统出现点问题,请稍后再试!"; message = "用户名或密码错误"; e.printStackTrace(); // 输出到控制台 } request.setAttribute(getFailureKeyAttribute(), className); request.setAttribute(getMessageParam(), message); return true; } } - 自定义SessionDAO
package top.z_f.simpleerp.config.shiro; import java.io.Serializable; import java.util.Collection; import java.util.HashSet; import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.session.Session; import org.apache.shiro.session.UnknownSessionException; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; import org.apache.shiro.session.mgt.eis.SessionDAO; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.support.DefaultSubjectContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import top.z_f.simpleerp.utils.common.ZfDateUtils; import top.z_f.simpleerp.utils.common.ZfStringUtils; import top.z_f.simpleerp.utils.sys.AppCode; import top.z_f.simpleerp.utils.sys.Servlets; /** * sessionDAO * @author internet/zhangzhen * @date 2019-06-13 */ public class CacheSessionDAO extends EnterpriseCacheSessionDAO implements SessionDAO { private Logger logger = LoggerFactory.getLogger(CacheSessionDAO.class); public CacheSessionDAO() { super(); } @Override protected void doUpdate(Session session) { logger.info("CacheSessionDAO.doUpdate() 1"); if (session == null || session.getId() == null) { return; } logger.info("CacheSessionDAO.doUpdate() 2"); HttpServletRequest request = Servlets.getRequest(); if (request != null){ String uri = request.getServletPath(); // 如果是静态文件,则不更新SESSION if (Servlets.isStaticFile(uri)){ return; } // 如果是请求的视图文件(网站静态文件),则不更新SESSION if (ZfStringUtils.startsWith(uri, AppCode.webStaticPrefix())){ return; } // 手动控制不更新SESSION String updateSession = request.getParameter("updateSession"); if (AppCode.FALSE.equals(updateSession) || AppCode.NO.equals(updateSession)){ return; } } super.doUpdate(session); logger.info("update {} {}", session.getId(), request != null ? request.getRequestURI() : ""); // TODO 更新数据库中的session } @Override protected void doDelete(Session session) { logger.info("CacheSessionDAO.doDelete() 1"); if (session == null || session.getId() == null) { return; } logger.info("CacheSessionDAO.doDelete() 2"); super.doDelete(session); logger.info("delete {} ", session.getId()); // TODO 从删除数据库中的session } @Override protected Serializable doCreate(Session session) { logger.info("CacheSessionDAO.doCreate() 1"); HttpServletRequest request = Servlets.getRequest(); if (request != null){ String uri = request.getServletPath(); // 如果是静态文件,则不创建SESSION if (Servlets.isStaticFile(uri)){ return null; } } logger.info("CacheSessionDAO.doCreate() 2"); super.doCreate(session); logger.info("doCreate {} {}", session, request != null ? request.getRequestURI() : ""); // TODO 将session保存到数据库 return session.getId(); } @Override protected Session doReadSession(Serializable sessionId) { logger.info("CacheSessionDAO.doReadSession()"); return super.doReadSession(sessionId); } @Override public Session readSession(Serializable sessionId) throws UnknownSessionException { logger.info("CacheSessionDAO.readSession() 1"); try{ Session s = null; HttpServletRequest request = Servlets.getRequest(); if (request != null){ String uri = request.getServletPath(); // 如果是静态文件,则不获取SESSION if (Servlets.isStaticFile(uri)){ return null; } s = (Session)request.getAttribute("session_"+sessionId); } logger.info("CacheSessionDAO.readSession() 2"); if (s != null){ return s; } // 默认从缓存中根据sessionid读取session信息 Session session = super.readSession(sessionId); logger.info("readSession {} {}", sessionId, request != null ? request.getRequestURI() : ""); // TODO 读取数据库中的session if (session == null) { // 根据sessionid从数据库读取session } if (request != null && session != null){ request.setAttribute("session_"+sessionId, session); } return session; }catch (UnknownSessionException e) { return null; } } /** * 获取活动会话 * @param includeLeave 是否包括离线(最后访问时间大于3分钟为离线会话) * @return */ public Collection<Session> getActiveSessions(boolean includeLeave) { return getActiveSessions(includeLeave, null, null); } /** * 获取活动会话 * @param includeLeave 是否包括离线(最后访问时间大于3分钟为离线会话) * @param principal 根据登录者对象获取活动会话 * @param filterSession 不为空,则过滤掉(不包含)这个会话。 * @return */ public Collection<Session> getActiveSessions(boolean includeLeave, Object principal, Session filterSession) { // 如果包括离线,并无登录者条件。 if (includeLeave && principal == null){ return getActiveSessions(); } Set<Session> sessions = new HashSet<Session>(); for (Session session : getActiveSessions()){ boolean isActiveSession = false; // 不包括离线并符合最后访问时间小于等于3分钟条件。 if (includeLeave || ZfDateUtils.pastMinutes(session.getLastAccessTime()) <= 3){ isActiveSession = true; } // 符合登陆者条件。 if (principal != null){ PrincipalCollection pc = (PrincipalCollection)session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); if (principal.toString().equals(pc != null ? pc.getPrimaryPrincipal().toString() : ZfStringUtils.EMPTY)){ isActiveSession = true; } } // 过滤掉的SESSION if (filterSession != null && filterSession.getId().equals(session.getId())){ isActiveSession = false; } if (isActiveSession){ sessions.add(session); } } return sessions; } } - 缓存文件
ehcache.xml配置<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd" updateCheck="false"> <diskStore path="java.io.tmpdir"/> <!-- realm缓存 --> <cache name="authorizationCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" overflowToDisk="false" statistics="false"> </cache> <!-- realm缓存 --> <cache name="authenticationCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" overflowToDisk="false" statistics="false"> </cache> <!-- session缓存 --> <cache name="activeSessionsCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" overflowToDisk="false" statistics="false"> </cache> <!-- 系统缓存 --> <cache name="sysCache" maxEntriesLocalHeap="100" eternal="true" overflowToDisk="true"/> <!-- 用户缓存 --> <cache name="userCache" maxEntriesLocalHeap="100" eternal="true" overflowToDisk="true"/> <!-- name:缓存名称。 maxElementsInMemory:缓存最大个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。 statistics:是否收集统计信息。如果需要监控缓存使用情况,应该打开这个选项。默认为关闭(统计会影响性能)。设置statistics="true"开启统计。 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 maxElementsOnDisk:硬盘最大缓存个数。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxElementsOnDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache> </ehcache> - 固定参数在
config.properties中配置# shiro中cookie失效时长,单位:s cookieTimeOut=7200 #session #会话超时时间(有效期), 单位:毫秒(ms), 20m=1200000ms, 30m=1800000ms, 60m=3600000ms session.sessionTimeout=1800000 #会话清理间隔时间, 单位:毫秒(ms),2m=120000ms。 session.sessionTimeoutClean=120000 #静态文件后缀 web.staticFileSuffix=.css,.js,.png,.jpg,.gif,.jpeg,.bmp,.ico,.swf,.psd,.htc,.crx,.xpi,.exe,.ipa,.apk #网站url后缀 urlSuffix=.html #网站静态资源前缀 web.static.prefix=/static/ - 相关类
- IdGenUtils.java
package top.z_f.simpleerp.utils.sys; import java.io.Serializable; import java.util.UUID; import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.eis.SessionIdGenerator; /** * 所有生成唯一性ID算法集合 * @author zhangzhen * @date 2019-06-14 */ public class IdGenUtils implements SessionIdGenerator { /** * * @return 32位uuid */ public static String getUUID32() { // 获取uuid,替换-号后,转成小写返回 String uuid32 = UUID.randomUUID().toString().replace("-", "").toLowerCase(); return uuid32; } @Override public Serializable generateId(Session session) { return IdGenUtils.getUUID32(); } } - Servlets.java
package top.z_f.simpleerp.utils.sys; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; /** * @author zhangzhen * @date 2019-06-13 */ public class Servlets { /** * 获取当前请求对象 * @return */ public static HttpServletRequest getRequest(){ try{ return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); }catch(Exception e){ return null; } } /** * 判断访问URI是否是静态文件请求 * @throws Exception */ public static boolean isStaticFile(String uri){ String[] staticFileSuffix = AppCode.staticFileSuffix(); if (staticFileSuffix == null){ try { throw new Exception("检测到“config.properties”中没有配置“web.staticFileSuffix”属性。配置示例:\n#静态文件后缀\n" +"web.staticFileSuffix=.css,.js,.png,.jpg,.gif,.jpeg,.bmp,.ico,.swf,.psd,.htc,.crx,.xpi,.exe,.ipa,.apk"); } catch (Exception e) { e.printStackTrace(); } } if (StringUtils.endsWithAny(uri, staticFileSuffix) && !StringUtils.endsWithAny(uri, AppCode.urlSuffix()) && !StringUtils.endsWithAny(uri, ".jsp") && !StringUtils.endsWithAny(uri, ".java")){ return true; } return false; } } - AppCode.java
package top.z_f.simpleerp.utils.sys; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.druid.util.StringUtils; /** * * 固定参数以及xxx.properties中配置参数值获取 * * @author zhangzhen * @date 2019-06-08 * */ public class AppCode { private static Logger logger = LoggerFactory.getLogger(AppCode.class); /** * 系统配置文件 */ private static PropertiesLoader propertiesConfig = new PropertiesLoader("config.properties"); /** * 用户密码加盐值 */ public static final String salt = "simpleErp"; /** * 是/否 */ public static final String YES = "1"; public static final String NO = "0"; /** * 对/错 */ public static final String TRUE = "true"; public static final String FALSE = "false"; ////////////////// shiro相关固定参数开始 /** * Cookie失效时常,单位:秒(s) * 没有配置的情况下默认两小时过期:2*60*60 * @return */ public static int cookieTimeOut() { logger.info("shiro cookie失效时长AppCode.cookieTimeOut==" + propertiesConfig.getProperty("cookieTimeOut")); Integer resultInt = StringUtils.stringToInteger(propertiesConfig.getProperty("cookieTimeOut")); return resultInt == null ? 2*60*60 : resultInt; } /** * 会话超时时间,单位:毫秒(ms) * 20m=1200000ms, 30m=1800000ms, 60m=3600000ms * 无配置的情况下默认30分钟 * @return */ public static int globalSessionTimeout() { logger.info("会话超时时间AppCode.globalSessionTimeout==" + propertiesConfig.getProperty("session.sessionTimeout")); Integer resultInt = StringUtils.stringToInteger(propertiesConfig.getProperty("session.sessionTimeout")); return resultInt == null ? 30*60*1000 : resultInt; } /** * 检查会话有效期间隔(即相隔多久检查一次session的有效性,失效清理) * 定时清理失效会话, 清理用户直接关闭浏览器造成的孤立会话; * 会话清理间隔时间, 单位:毫秒, * 2m=120000ms * 无配置的情况下默认2分钟 * @return */ public static int sessionValidationInterval() { logger.info("会话清理间隔时间AppCode.sessionValidationInterval==" + propertiesConfig.getProperty("session.sessionTimeoutClean")); Integer resultInt = StringUtils.stringToInteger(propertiesConfig.getProperty("session.sessionTimeoutClean")); return resultInt == null ? 2*60*1000 : resultInt; } ////////////////// shiro相关固定参数结束 /** * 静态文件后缀web.staticFileSuffix * @return */ public static String[] staticFileSuffix() { String staticFileSuffix = propertiesConfig.getProperty("web.staticFileSuffix"); return org.apache.commons.lang3.StringUtils.split(staticFileSuffix); } /** * 网站URL后缀 * @return */ public static String urlSuffix() { return propertiesConfig.getProperty("urlSuffix"); } /** * 网站静态资源前缀 * @return */ public static String webStaticPrefix() { return propertiesConfig.getProperty("web.static.prefix"); } } - PropertiesLoader.java
/** * Copyright (c) 2005-2011 springside.org.cn * * $Id: PropertiesLoader.java 1690 2012-02-22 13:42:00Z calvinxiu $ */ package top.z_f.simpleerp.utils.sys; import java.io.IOException; import java.io.InputStream; import java.util.NoSuchElementException; import java.util.Properties; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; /** * Properties文件载入工具类. 可载入多个properties文件, 相同的属性在最后载入的文件中的值将会覆盖之前的值,但以System的Property优先. * @author calvin * @version 2013-05-15 */ public class PropertiesLoader { private static Logger logger = LoggerFactory.getLogger(PropertiesLoader.class); private static ResourceLoader resourceLoader = new DefaultResourceLoader(); private final Properties properties; public PropertiesLoader(String... resourcesPaths) { properties = loadProperties(resourcesPaths); } public Properties getProperties() { return properties; } /** * 取出Property,但以System的Property优先,取不到返回空字符串. */ private String getValue(String key) { String systemProperty = System.getProperty(key); if (systemProperty != null) { return systemProperty; } if (properties.containsKey(key)) { return properties.getProperty(key); } return ""; } /** * 取出String类型的Property,但以System的Property优先,如果都为Null则抛出异常. */ public String getProperty(String key) { String value = getValue(key); if (value == null) { throw new NoSuchElementException(); } return value; } /** * 取出String类型的Property,但以System的Property优先.如果都为Null则返回Default值. */ public String getProperty(String key, String defaultValue) { String value = getValue(key); return value != null ? value : defaultValue; } /** * 取出Integer类型的Property,但以System的Property优先.如果都为Null或内容错误则抛出异常. */ public Integer getInteger(String key) { String value = getValue(key); if (value == null) { throw new NoSuchElementException(); } return Integer.valueOf(value); } /** * 取出Integer类型的Property,但以System的Property优先.如果都为Null则返回Default值,如果内容错误则抛出异常 */ public Integer getInteger(String key, Integer defaultValue) { String value = getValue(key); return value != null ? Integer.valueOf(value) : defaultValue; } /** * 取出Double类型的Property,但以System的Property优先.如果都为Null或内容错误则抛出异常. */ public Double getDouble(String key) { String value = getValue(key); if (value == null) { throw new NoSuchElementException(); } return Double.valueOf(value); } /** * 取出Double类型的Property,但以System的Property优先.如果都为Null则返回Default值,如果内容错误则抛出异常 */ public Double getDouble(String key, Integer defaultValue) { String value = getValue(key); return value != null ? Double.valueOf(value) : defaultValue; } /** * 取出Boolean类型的Property,但以System的Property优先.如果都为Null抛出异常,如果内容不是true/false则返回false. */ public Boolean getBoolean(String key) { String value = getValue(key); if (value == null) { throw new NoSuchElementException(); } return Boolean.valueOf(value); } /** * 取出Boolean类型的Property,但以System的Property优先.如果都为Null则返回Default值,如果内容不为true/false则返回false. */ public Boolean getBoolean(String key, boolean defaultValue) { String value = getValue(key); return value != null ? Boolean.valueOf(value) : defaultValue; } /** * 载入多个文件, 文件路径使用Spring Resource格式. */ private Properties loadProperties(String... resourcesPaths) { Properties props = new Properties(); for (String location : resourcesPaths) { // logger.debug("Loading properties file from:" + location); InputStream is = null; try { Resource resource = resourceLoader.getResource(location); is = resource.getInputStream(); props.load(is); } catch (IOException ex) { logger.info("Could not load properties from path:" + location + ", " + ex.getMessage()); } finally { IOUtils.closeQuietly(is); } } return props; } }
- IdGenUtils.java
- 登录编写(主体不变,具体根据需要进行修改)
/** * HttpServletRequest获取参数的登录处理 * @param request * @param model * @return */ @RequestMapping("/z/login") public String login(HttpServletRequest request, HttpServletResponse response, Model model) { logger.info("WebController.login()"); String username = WebUtils.getCleanParam(request, FormAuthenticationFilter.DEFAULT_USERNAME_PARAM); String password = WebUtils.getCleanParam(request, FormAuthenticationFilter.DEFAULT_PASSWORD_PARAM); String message = (String)request.getAttribute(FormAuthenticationFilter.DEFAULT_MESSAGE_PARAM); boolean rememberMe = WebUtils.isTrue(request, FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM); if (StringUtils.isBlank(message)) { message = ""; } Subject subject = SecurityUtils.getSubject(); // 判断是否为真正的登录(用户名和密码必须不能为空) if (StringUtils.isBlank(username) || StringUtils.isBlank(password) || StringUtils.isNotEmpty(message)) { if (subject.isAuthenticated()) { return "redirect:/z"; } else if (subject.isRemembered()) { UserShiroUtils.RefeshShiro(); return "redirect:/z"; } } model.addAttribute(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM, StringUtils.isBlank(username) ? "" : username); model.addAttribute(FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM, rememberMe); model.addAttribute(FormAuthenticationFilter.DEFAULT_MESSAGE_PARAM, message); return "login_register/login"; } - 启动程序,用其他url看是否会跳转到login页面
写在末尾
- 实现了正常登录和RememberMe登录
- 配置了session,RememberMe的cookie在session中配置
- 登录成功之后,获得zf.session.id的值,直接
http://127.0.0.1:8089/xxx?zf.session.id=xxxxxxxxxx即可获取资源(app、接口调用可用) - WebController.login(默认打开跳转路径) –> FormAuthenticationFilter.createToken –> ManagerUserShiroRealm.doGetAuthenticationInfo –> FormAuthenticationFilter.onLoginSuccess(从shiroFilter中读取登录成功后跳转路径)
参考文档
- Shiro
- Spring Boot 整合 Shiro-登录认证和权限管理
- SpringBoot集成Shiro
- login demo
- RememberMe参考文档1
- RememberMe参考文档2
- shiro配置简单介绍
- 细说shiro之六:session管理
- subject 获取登录用户信息
- Ehcache 01
- Ehcache 02
- freemarker使用shiro标签(spring boot)