基于springboot跟shiro的后台权限管理系统
技术:springboot1.5.1 + shiro1.4.0 + maven3.0.5 +jdk1.8.0
概述
SpringBoot整合Shiro,Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能。
详细
一、运行效果
二、实现过程
①、创建shiro配置
@Configuration public class ShiroConfiguration { private Logger logger = LoggerFactory.getLogger(DataSourceConfig.class); /** * ShiroFilterFactoryBean 处理拦截资源文件问题。 * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在 * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager * * Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过 * 3、部分过滤器可指定参数,如perms,roles * */ @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { logger.info("ShiroConfiguration.shiroFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 拦截器. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/admin/sys/logout", "logout"); // 配置记住我或认证通过可以访问的地址 filterChainDefinitionMap.put("/admin/index", "user"); // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/statics/**", "anon"); filterChainDefinitionMap.put("/admin/captcha.jpg", "anon"); filterChainDefinitionMap.put("/admin/sys/login", "anon"); filterChainDefinitionMap.put("/admin/sys/logout", "anon"); filterChainDefinitionMap.put("/admin/**", "authc"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/admin/login.html"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/admin/"); // 未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/error.html"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(getUserRealm()); // 注入缓存管理器; 这个如果执行多次,也是同样的一个对象; securityManager.setCacheManager(ehCacheManager()); // 注入记住我管理器; securityManager.setRememberMeManager(rememberMeManager()); return securityManager; } /** * 身份认证realm; (这个需要自己写,账号密码校验;权限等) * * @return */ @Bean public UserRealm getUserRealm() { UserRealm myShiroRealm = new UserRealm(); return myShiroRealm; } /** * 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持 * * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * shiro缓存管理器; 需要注入对应的其它的实体类中: 1、安全管理器:securityManager * 可见securityManager是整个shiro的核心 * * @return */ @Bean public EhCacheManager ehCacheManager() { logger.info("ShiroConfiguration.getEhCacheManager()"); EhCacheManager cacheManager = new EhCacheManager(); cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); return cacheManager; } /** * cookie对象 * * @return */ @Bean public SimpleCookie rememberMeCookie() { logger.info("ShiroConfiguration.rememberMeCookie()"); // 这个参数是cookie的名称,对应前端的checkbox 的name = rememberMe SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); // <!-- 记住我cookie生效时间30天 ,单位秒;--> simpleCookie.setMaxAge(259200); return simpleCookie; } /** * cookie管理对象 * * @return */ @Bean public CookieRememberMeManager rememberMeManager() { logger.info("ShiroConfiguration.rememberMeManager()"); CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); byte[] cipherKey = Base64.decode("wGiHplamyXlVB11UXWol8g=="); cookieRememberMeManager.setCipherKey(cipherKey); return cookieRememberMeManager; } }
②、自定义userRealm
/** * Shiro认证 * */ public class UserRealm extends AuthorizingRealm { private Logger logger = LoggerFactory.getLogger(UserRealm.class); /** * 授权(验证权限时调用) */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info("授权(验证权限时调用)"); SysUserEntity user = (SysUserEntity) principals.getPrimaryPrincipal(); Long userId = user.getUserId(); List<String> permsList = null; // 系统管理员,拥有最高权限 if (userId.equals(TempUtil.constant.adminId)) { List<SysMenuEntity> menuList = TempUtil.sysMenuService.queryList(new HashMap<String, Object>(10)); permsList = new ArrayList<>(menuList.size()); for (SysMenuEntity menu : menuList) { permsList.add(menu.getPerms()); } } else { permsList = TempUtil.sysUserService.queryAllPerms(userId); } // 用户权限列表 Set<String> permsSet = new HashSet<String>(); for (String perms : permsList) { if (StringUtils.isBlank(perms)) { continue; } permsSet.addAll(Arrays.asList(perms.trim().split(","))); } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setStringPermissions(permsSet); return info; } /** * 认证(登录时调用) */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { logger.info("认证(登录时调用)"); String username = (String) token.getPrincipal(); String password = new String((char[]) token.getCredentials()); // 查询用户信息 SysUserEntity user = TempUtil.sysUserService.queryByUserName(username); // 账号不存在 if (user == null) { throw new UnknownAccountException("账号或密码不正确"); } // 密码错误 if (!password.equals(user.getPassword())) { throw new IncorrectCredentialsException("账号或密码不正确"); } // 账号锁定 if (user.getStatus() == 0) { throw new LockedAccountException("账号已被锁定,请联系管理员"); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName()); return info; } }
③、登录接口
@ResponseBody @RequestMapping(value = "/sys/login", method = RequestMethod.POST) @Transactional(rollbackOn = Exception.class) public R login(String username, String password, String captcha, HttpServletRequest request) throws IOException { if (StringUtils.isBlank(username)) { return R.error("请输入用户名!"); } if (StringUtils.isBlank(password)) { return R.error("请输入密码!"); } String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY); if (!captcha.equalsIgnoreCase(kaptcha)) { return R.error("验证码不正确"); } try { Subject subject = ShiroUtils.getSubject(); // sha256加密 password = new Sha256Hash(password).toHex(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); subject.login(token); } catch (UnknownAccountException e) { return R.error(e.getMessage()); } catch (IncorrectCredentialsException e) { return R.error(e.getMessage()); } catch (LockedAccountException e) { return R.error(e.getMessage()); } catch (AuthenticationException e) { e.printStackTrace(); return R.error("账户验证失败"); } SysUserEntity user = getAdmin(); // 登录IP String ipAddress = GetIpAddress.getIpAddress(request); // 当前时间戳 Long currentUnixTime = DateUtils.getCurrentUnixTime(); user.setLastLoginIp(ipAddress); user.setLastLoginTime(currentUnixTime); boolean updateById = sysUserService.updateById(user); if (!updateById) { return R.error("系统异常,请联系管理员!"); } // 记录登录日志 SysUserLoginLogEntity entity = new SysUserLoginLogEntity(); entity.setBrowser(HttpUtil.getUserBrowser(request)); entity.setLoginTime(currentUnixTime); entity.setOperatingSystem(HttpUtil.getUserOperatingSystem(request)); entity.setUserId(getAdminId()); entity.setLoginIp(ipAddress); boolean insert = sysUserLoginLogService.insert(entity); // 这里只能抛异常回滚事务 if (!insert) { throw new RRException("系统异常,请联系管理员!"); } return R.ok(); }
④、缓存机制
<?xml version="1.0" encoding="UTF-8"?> <ehcache name="es"> <diskStore path="java.io.tmpdir"/> <!-- name:缓存名称。 maxElementsInMemory:缓存最大数目 maxElementsOnDisk:硬盘最大缓存个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:是否保存到磁盘,当系统当机时 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 memoryStoreEvictionPolicy: Ehcache的三种清空策略; FIFO,first in first out,这个是大家最熟的,先进先出。 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> <!-- 登录记录缓存 锁定10分钟 --> <cache name="passwordRetryCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> </ehcache>
三、项目结构图
四、补充
根据Shiro的设计思路,用户与角色之前的关系为多对多,角色与权限之间的关系也是多对多。在数据库中需要因此建立5张表,分别是用户表(存储用户名,密码,盐等)、角色表(角色名称,相关描述等)、权限表(权限名称,相关描述等)、用户-角色对应中间表(以用户ID和角色ID作为联合主键)、角色-权限对应中间表(以角色ID和权限ID作为联合主键)。具体dao与service的实现本文不提供。总之结论就是,Shiro需要根据用户名和密码首先判断登录的用户是否合法,然后再对合法用户授权。而这个过程就是Realm的实现过程。
本实例支付的费用只是购买源码的费用,如有疑问欢迎在文末留言交流,如需作者在线代码指导、定制等,在作者开启付费服务后,可以点击“购买服务”进行实时联系,请知悉,谢谢
手机上随时阅读、收藏该文章 ?请扫下方二维码