blogSite

我是谁?我在哪儿?我在干什么?

View project on GitHub

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.java
      package 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;
            }
        }
      
  • 登录编写(主体不变,具体根据需要进行修改)
    
      /**
       * 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中读取登录成功后跳转路径)

参考文档

上一篇:spring boot使用Redis 下一篇:spring boot配置默认页面

首页 > 学习总览 > 开发语言 > Java