SpringBoot学习:整合shiro(身份认证和权限认证),使用EhCache缓存
项目下载地址:http://download.csdn.NET/detail/aqsunkai/9805821
(一)在pom.xml中添加依赖:
<properties> <shiro.version>1.3.2</shiro.version> </properties>
<!--shiro start--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <!--shiro end-->
下面是数据库的表,这里主要涉及到五张表:用户表,角色表(用户所拥有的角色),权限表(角色所涉及到的权限),用户-角色表(用户和角色是多对多的),角色-权限表(角色和权限是多对多的)。sql语句如下:
-- ---------------------------- -- Table structure for sys_permission -- ---------------------------- DROP TABLE IF EXISTS `sys_permission`; CREATE TABLE `sys_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `url` varchar(256) DEFAULT NULL COMMENT 'url地址', `name` varchar(64) DEFAULT NULL COMMENT 'url描述/名称', parent_id int(11) DEFAULT NULL COMMENT '父节点权限ID', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of sys_permission -- ---------------------------- INSERT INTO `sys_permission` VALUES ('10', '/member/changeSessionStatus.shtml', '用户Session踢出',null); INSERT INTO `sys_permission` VALUES ('11', '/member/forbidUserById.shtml', '用户激活&禁止',null); INSERT INTO `sys_permission` VALUES ('12', '/member/deleteUserById.shtml', '用户删除',null); INSERT INTO `sys_permission` VALUES ('13', '/permission/addPermission2Role.shtml', '权限分配',null); INSERT INTO `sys_permission` VALUES ('14', '/role/clearRoleByUserIds.shtml', '用户角色分配清空',null); INSERT INTO `sys_permission` VALUES ('15', '/role/addRole2User.shtml', '角色分配保存',null); INSERT INTO `sys_permission` VALUES ('16', '/role/deleteRoleById.shtml', '角色列表删除',null); INSERT INTO `sys_permission` VALUES ('17', '/role/addRole.shtml', '角色列表添加',null); INSERT INTO `sys_permission` VALUES ('18', '/role/index.shtml', '角色列表',null); INSERT INTO `sys_permission` VALUES ('19', '/permission/allocation.shtml', '权限分配',null); INSERT INTO `sys_permission` VALUES ('20', '/role/allocation.shtml', '角色分配',null); INSERT INTO `sys_permission` VALUES ('4', '/permission/index.shtml', '权限列表',null); INSERT INTO `sys_permission` VALUES ('6', '/permission/addPermission.shtml', '权限添加',null); INSERT INTO `sys_permission` VALUES ('7', '/permission/deletePermissionById.shtml', '权限删除',null); INSERT INTO `sys_permission` VALUES ('8', '/member/list.shtml', '用户列表',null); INSERT INTO `sys_permission` VALUES ('9', '/member/online.shtml', '在线用户',null); -- ---------------------------- -- Table structure for sys_role -- ---------------------------- DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(32) DEFAULT NULL COMMENT '角色名称', `type` varchar(10) DEFAULT NULL COMMENT '角色类型', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of sys_role -- ---------------------------- INSERT INTO `sys_role` VALUES ('1', '系统管理员', '100004'); INSERT INTO `sys_role` VALUES ('3', '权限角色', '100001'); INSERT INTO `sys_role` VALUES ('4', '用户中心', '100002'); INSERT INTO `sys_role` VALUES ('0', '角色管理', '100003'); -- ---------------------------- -- Table structure for sys_role_permission -- ---------------------------- DROP TABLE IF EXISTS `sys_role_permission`; CREATE TABLE `sys_role_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `rid` varchar(64) DEFAULT NULL COMMENT '角色ID', `pid` varchar(64) DEFAULT NULL COMMENT '权限ID', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of sys_role_permission -- ---------------------------- INSERT INTO `sys_role_permission` VALUES ('1', '4', '8'); INSERT INTO `sys_role_permission` VALUES ('10', '3', '14'); INSERT INTO `sys_role_permission` VALUES ('11', '3', '15'); INSERT INTO `sys_role_permission` VALUES ('12', '3', '16'); INSERT INTO `sys_role_permission` VALUES ('13', '3', '17'); INSERT INTO `sys_role_permission` VALUES ('14', '3', '18'); INSERT INTO `sys_role_permission` VALUES ('15', '3', '19'); INSERT INTO `sys_role_permission` VALUES ('16', '3', '20'); INSERT INTO `sys_role_permission` VALUES ('17', '1', '4'); INSERT INTO `sys_role_permission` VALUES ('18', '1', '6'); INSERT INTO `sys_role_permission` VALUES ('19', '1', '7'); INSERT INTO `sys_role_permission` VALUES ('2', '4', '9'); INSERT INTO `sys_role_permission` VALUES ('20', '1', '8'); INSERT INTO `sys_role_permission` VALUES ('21', '1', '9'); INSERT INTO `sys_role_permission` VALUES ('22', '1', '10'); INSERT INTO `sys_role_permission` VALUES ('23', '1', '11'); INSERT INTO `sys_role_permission` VALUES ('24', '1', '12'); INSERT INTO `sys_role_permission` VALUES ('25', '1', '13'); INSERT INTO `sys_role_permission` VALUES ('26', '1', '14'); INSERT INTO `sys_role_permission` VALUES ('27', '1', '15'); INSERT INTO `sys_role_permission` VALUES ('28', '1', '16'); INSERT INTO `sys_role_permission` VALUES ('29', '1', '17'); INSERT INTO `sys_role_permission` VALUES ('3', '4', '10'); INSERT INTO `sys_role_permission` VALUES ('30', '1', '18'); INSERT INTO `sys_role_permission` VALUES ('31', '1', '19'); INSERT INTO `sys_role_permission` VALUES ('32', '1', '20'); INSERT INTO `sys_role_permission` VALUES ('4', '4', '11'); INSERT INTO `sys_role_permission` VALUES ('5', '4', '12'); INSERT INTO `sys_role_permission` VALUES ('6', '3', '4'); INSERT INTO `sys_role_permission` VALUES ('7', '3', '6'); INSERT INTO `sys_role_permission` VALUES ('8', '3', '7'); INSERT INTO `sys_role_permission` VALUES ('9', '3', '13'); -- ---------------------------- -- Table structure for sys_user -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `nickname` varchar(20) DEFAULT NULL COMMENT '用户昵称', `email` varchar(128) DEFAULT NULL COMMENT '邮箱|登录帐号', `pswd` varchar(255) DEFAULT NULL COMMENT '密码', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', `status` TINYINT DEFAULT '1' COMMENT '1:有效,0:禁止登录', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of sys_user -- ---------------------------- INSERT INTO `sys_user` VALUES ('1', 'admin', 'admin@qq.com', '123456', NOW(), NOW(), '1'); INSERT INTO `sys_user` VALUES ('11', 'root', '8446666@qq.com', '123456', '2016-05-26 20:50:54', '2017-02-13 15:49:04', '1'); INSERT INTO `sys_user` VALUES ('12', '8446666', '8446666', 'CpievEp3tWpuK7exnZldGFzkQJDBPimEt+zG1EbUth6pmRt2pMLwSxtNJEhBRJRU', '2016-05-27 22:34:19', '2016-06-15 17:03:16', '1'); INSERT INTO `sys_user` VALUES ('13', '123', '123', 'CpievEp3tWpuK7exnZldGFzkQJDBPimEt+zG1EbUth6pmRt2pMLwSxtNJEhBRJRU', '2016-05-27 22:34:19', '2016-06-15 17:03:16', '0'); INSERT INTO `sys_user` VALUES ('14', 'haiqin', '123123@qq.com', 'CpievEp3tWpuK7exnZldGFzkQJDBPimEt+zG1EbUth6pmRt2pMLwSxtNJEhBRJRU', '2016-05-27 22:34:19', '2017-03-23 21:39:44', '1'); -- ---------------------------- -- Table structure for sys_user_role -- ---------------------------- DROP TABLE IF EXISTS `sys_user_role`; CREATE TABLE `sys_user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `uid` varchar(64) DEFAULT NULL COMMENT '用户ID', `rid` varchar(64) DEFAULT NULL COMMENT '角色ID', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of sys_user_role -- ---------------------------- INSERT INTO `sys_user_role` VALUES ('1', '12', '4'); INSERT INTO `sys_user_role` VALUES ('2', '11', '3'); INSERT INTO `sys_user_role` VALUES ('3', '11', '4'); INSERT INTO `sys_user_role` VALUES ('4', '1', '1');
(二)shiro配置类:
package com.sun.configuration; import org.apache.log4j.Logger; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; import java.util.LinkedHashMap; import java.util.Map; /** * Shiro 配置 * Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 * 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。 * Created by sun on 2017-4-2. */ @Configuration @EnableTransactionManagement public class ShiroConfiguration{ private final Logger logger = Logger.getLogger(ShiroConfiguration.class); /** * ShiroFilterFactoryBean 处理拦截资源文件问题。 * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在 * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager * Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过 3、部分过滤器可指定参数,如perms,roles * */ @Bean public EhCacheManager getEhCacheManager(){ EhCacheManager ehcacheManager = new EhCacheManager(); ehcacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml"); return ehcacheManager; } @Bean(name = "myShiroRealm") public MyShiroRealm myShiroRealm(EhCacheManager ehCacheManager){ MyShiroRealm realm = new MyShiroRealm(); realm.setCacheManager(ehCacheManager); return realm; } @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ return new LifecycleBeanPostProcessor(); } @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); creator.setProxyTargetClass(true); return creator; } @Bean(name = "securityManager") public DefaultWebSecurityManager defaultWebSecurityManager(MyShiroRealm realm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //设置realm securityManager.setRealm(realm); securityManager.setCacheManager(getEhCacheManager()); return securityManager; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean factoryBean = new MyShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 factoryBean.setLoginUrl("/login"); // 登录成功后要跳转的连接 factoryBean.setSuccessUrl("/welcome"); factoryBean.setUnauthorizedUrl("/403"); loadShiroFilterChain(factoryBean); logger.info("shiro拦截器工厂类注入成功"); return factoryBean; } /** * 加载ShiroFilter权限控制规则 */ private void loadShiroFilterChain(ShiroFilterFactoryBean factoryBean) { /**下面这些规则配置最好配置到配置文件中*/ Map<String, String> filterChainMap = new LinkedHashMap<String, String>(); /** authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器 * org.apache.shiro.web.filter.authc.FormAuthenticationFilter */ // anon:它对应的过滤器里面是空的,什么都没做,可以理解为不拦截 //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问 filterChainMap.put("/permission/userInsert", "anon"); filterChainMap.put("/error", "anon"); filterChainMap.put("/tUser/insert","anon"); filterChainMap.put("/**", "authc"); factoryBean.setFilterChainDefinitionMap(filterChainMap); } /*1.LifecycleBeanPostProcessor,这是个DestructionAwareBeanPostProcessor的子类,负责org.apache.shiro.util.Initializable类型bean的生命周期的,初始化和销毁。主要是AuthorizingRealm类的子类,以及EhCacheManager类。 2.HashedCredentialsMatcher,这个类是为了对密码进行编码的,防止密码在数据库里明码保存,当然在登陆认证的生活,这个类也负责对form里输入的密码进行编码。 3.ShiroRealm,这是个自定义的认证类,继承自AuthorizingRealm,负责用户的认证和权限的处理,可以参考JdbcRealm的实现。 4.EhCacheManager,缓存管理,用户登陆成功后,把用户信息和权限信息缓存起来,然后每次用户请求时,放入用户的session中,如果不设置这个bean,每个请求都会查询一次数据库。 5.SecurityManager,权限管理,这个类组合了登陆,登出,权限,session的处理,是个比较重要的类。 6.ShiroFilterFactoryBean,是个factorybean,为了生成ShiroFilter。它主要保持了三项数据,securityManager,filters,filterChainDefinitionManager。 7.DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。 8.AuthorizationAttributeSourceAdvisor,shiro里实现的Advisor类,内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。*/ }
package com.sun.configuration; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.filter.mgt.FilterChainManager; import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; import org.apache.shiro.web.mgt.WebSecurityManager; import org.apache.shiro.web.servlet.AbstractShiroFilter; import org.springframework.beans.factory.BeanInitializationException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.HashSet; import java.util.Set; /** * Created by sun on 2017-4-2. */ public class MyShiroFilterFactoryBean extends ShiroFilterFactoryBean { // ShiroFilter将直接忽略的请求 private Set<String> ignoreExt; public MyShiroFilterFactoryBean(){ super(); ignoreExt = new HashSet<String>(); ignoreExt.add(".jpg"); ignoreExt.add(".png"); ignoreExt.add(".gif"); ignoreExt.add(".bmp"); ignoreExt.add(".js"); ignoreExt.add(".css"); } /** * 启动时加载 */ @Override protected AbstractShiroFilter createInstance() throws Exception { SecurityManager securityManager = getSecurityManager(); if (securityManager == null){ throw new BeanInitializationException("SecurityManager property must be set."); } if (!(securityManager instanceof WebSecurityManager)){ throw new BeanInitializationException("The security manager does not implement the WebSecurityManager interface."); } PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); FilterChainManager chainManager = createFilterChainManager(); chainResolver.setFilterChainManager(chainManager); return new MySpringShiroFilter((WebSecurityManager)securityManager, chainResolver); } /** * 启动时加载 */ private class MySpringShiroFilter extends AbstractShiroFilter { public MySpringShiroFilter( WebSecurityManager securityManager, PathMatchingFilterChainResolver chainResolver) { super(); if (securityManager == null){ throw new IllegalArgumentException("WebSecurityManager property cannot be null."); } setSecurityManager(securityManager); if (chainResolver != null){ setFilterChainResolver(chainResolver); } } /** * 页面上传输的url先进入此方法验证 */ @Override protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest)servletRequest; String str = request.getRequestURI().toLowerCase(); boolean flag = true; int idx = 0; if ((idx = str.lastIndexOf(".")) > 0){ str = str.substring(idx); if (ignoreExt.contains(str.toLowerCase())){ flag = false; } } if (flag){ super.doFilterInternal(servletRequest, servletResponse, chain); } else { chain.doFilter(servletRequest, servletResponse); } } } }
package com.sun.configuration; import com.sun.permission.model.Role; import com.sun.permission.model.User; import com.sun.permission.service.PermissionService; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.log4j.Logger; import org.apache.shiro.authc.*; 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.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * shiro的认证最终是交给了Realm进行执行 * 所以我们需要自己重新实现一个Realm,此Realm继承AuthorizingRealm * Created by sun on 2017-4-2. */ public class MyShiroRealm extends AuthorizingRealm { private static final Logger logger = Logger.getLogger(MyShiroRealm.class); @Autowired private PermissionService permissionService; /** * 登录认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //UsernamePasswordToken用于存放提交的登录信息 UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; logger.info("登录认证!"); logger.info("验证当前Subject时获取到token为:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE)); User user = permissionService.findByUserEmail(token.getUsername()); if (user != null){ logger.info("用户: " + user.getEmail()); if(user.getStatus() == 0){ throw new DisabledAccountException(); } // 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验 return new SimpleAuthenticationInfo(user.getEmail(), user.getPswd(), getName()); } return null; } /** * 权限认证(为当前登录的Subject授予角色和权限) * * 该方法的调用时机为需授权资源被访问时,并且每次访问需授权资源都会执行该方法中的逻辑,这表明本例中并未启用AuthorizationCache, * 如果连续访问同一个URL(比如刷新),该方法不会被重复调用,Shiro有一个时间间隔(也就是cache时间,在ehcache-shiro.xml中配置), * 超过这个时间间隔再刷新页面,该方法会被执行 * * doGetAuthorizationInfo()是权限控制, * 当访问到页面的时候,使用了相应的注解或者shiro标签才会执行此方法否则不会执行, * 所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String loginName = (String) super.getAvailablePrincipal(principals); User user = permissionService.findByUserEmail(loginName); logger.info("权限认证!"); if (user != null){ // 权限信息对象info,用来存放查出的用户的所有的角色及权限 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //用户的角色集合 info.setRoles(permissionService.getRolesName(user.getId())); List<Role> roleList = permissionService.getRoleList(user.getId()); for (Role role : roleList){ //用户的角色对应的所有权限 logger.info("角色: "+role.getName()); info.addStringPermissions(permissionService.getPermissionsName(role.getId())); } return info; } // 返回null将会导致用户访问任何被拦截的请求时都会自动跳转到unauthorizedUrl指定的地址 return null; } }
ehcache-shiro.xml内容:
<?xml version="1.0" encoding="UTF-8"?> <ehcache updateCheck="false" name="shiroCache"> <!-- 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>
登录的controller类如下:
package com.sun.permission.controller; import com.sun.permission.model.User; import com.sun.permission.service.PermissionService; import com.sun.util.CommonUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.validation.Valid; /** * Created by sun on 2017-4-2. */ @Controller public class LoginController { private static final Logger logger = Logger.getLogger(LoginController.class); @Autowired private PermissionService permissionService; @RequestMapping(value="/login",method= RequestMethod.GET) public ModelAndView loginForm(){ ModelAndView model = new ModelAndView(); model.addObject("user", new User()); model.setViewName("login"); return model; } @RequestMapping(value="/login",method=RequestMethod.POST) public String login(@Valid User user, BindingResult bindingResult, RedirectAttributes redirectAttributes){ if(bindingResult.hasErrors()){ return "redirect:login"; } String email = user.getEmail(); if(StringUtils.isBlank(user.getEmail()) || StringUtils.isBlank(user.getPswd())){ logger.info("用户名或密码为空! "); redirectAttributes.addFlashAttribute("message", "用户名或密码为空!"); return "redirect:login"; } //验证 UsernamePasswordToken token = new UsernamePasswordToken(user.getEmail(), user.getPswd()); //获取当前的Subject Subject currentUser = SecurityUtils.getSubject(); try { //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查 //每个Realm都能在必要时对提交的AuthenticationTokens作出反应 //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法 logger.info("对用户[" + email + "]进行登录验证..验证开始"); currentUser.login(token); logger.info("对用户[" + email + "]进行登录验证..验证通过"); }catch(UnknownAccountException uae){ logger.info("对用户[" + email + "]进行登录验证..验证未通过,未知账户"); redirectAttributes.addFlashAttribute("message", "未知账户"); }catch(IncorrectCredentialsException ice){ logger.info("对用户[" + email + "]进行登录验证..验证未通过,错误的凭证"); redirectAttributes.addFlashAttribute("message", "密码不正确"); }catch(LockedAccountException lae){ logger.info("对用户[" + email + "]进行登录验证..验证未通过,账户已锁定"); redirectAttributes.addFlashAttribute("message", "账户已锁定"); }catch(ExcessiveAttemptsException eae){ logger.info("对用户[" + email + "]进行登录验证..验证未通过,错误次数大于5次,账户已锁定"); redirectAttributes.addFlashAttribute("message", "用户名或密码错误次数大于5次,账户已锁定"); }catch (DisabledAccountException sae){ logger.info("对用户[" + email + "]进行登录验证..验证未通过,帐号已经禁止登录"); redirectAttributes.addFlashAttribute("message", "帐号已经禁止登录"); }catch(AuthenticationException ae){ //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景 logger.info("对用户[" + email + "]进行登录验证..验证未通过,堆栈轨迹如下"); ae.printStackTrace(); redirectAttributes.addFlashAttribute("message", "用户名或密码不正确"); } //验证是否登录成功 if(currentUser.isAuthenticated()){ logger.info("用户[" + email + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)"); //把当前用户放入session Session session = currentUser.getSession(); User tUser = permissionService.findByUserEmail(email); session.setAttribute("currentUser",tUser); return "/welcome"; }else{ token.clear(); return "redirect:login"; } } @RequestMapping(value="/logout",method=RequestMethod.GET) public String logout(RedirectAttributes redirectAttributes ){ //使用权限管理工具进行用户的退出,跳出登录,给出提示信息 SecurityUtils.getSubject().logout(); redirectAttributes.addFlashAttribute("message", "您已安全退出"); return "redirect:login"; } @RequestMapping("/403") public String unauthorizedRole(){ logger.info("------没有权限-------"); return "errorPermission"; } }
login.jsp如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>Login</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> <!-- <link rel="stylesheet" type="text/css" href="styles.css"> --> <script src="<%=basePath%>js/jquery-2.1.4/jquery.min.js"></script> </head> <body style="margin-left: 500px"> <h1 style="margin-left: 30px">登录页面----</h1> <form action="<%=basePath%>/login" method="post"> 用户名 : <input type="text" name="email" id="email"/><br> 密码: <input type="password" name="pswd" id="pswd"/><br> <input style="margin-left: 100px" type="submit" value="登录"/><input style="left: 50px" onclick="register()" type="button" value="注册"/> </form> <h1 style="color: red">${message }</h1> </body> <script type="text/javascript"> function register(){ location.href="<%=basePath%>permission/userInsert"; } </script> </html>
welcome.jsp如下:
<%-- Created by IntelliJ IDEA. User: sun Date: 2017-4-2 Time: 20:17 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%--<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>--%> <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <html> <head> <title>欢迎!</title> </head> <body style="text-align: center"> <h1>${message }</h1> <h1>用户列表--<a href="${pageContext.request.contextPath }/logout">退出登录</a></h1> 当前登录用户: <shiro:principal/><br/> <shiro:authenticated>我已登录,但未记住<br/></shiro:authenticated> <shiro:user>我已登录,或已记住<br/></shiro:user> <shiro:guest>我是访客<br/></shiro:guest> <shiro:hasAnyRoles name="manager,admin">manager or admin 角色用户登录显示此内容<br/></shiro:hasAnyRoles> <shiro:hasRole name="系统管理员">我是系统管理员<br/></shiro:hasRole> <shiro:hasRole name="会员">我是会员<br/></shiro:hasRole> <h2>权限列表</h2> <shiro:hasPermission name="权限列表">具有权限列表权限用户显示此内容<br/></shiro:hasPermission> <shiro:hasPermission name="用户列表">具有用户列表权限用户显示此内容<br/></shiro:hasPermission> <shiro:hasPermission name="在线用户">具有在线用户权限用户显示此内容<br/></shiro:hasPermission> <shiro:lacksPermission name="角色分配保存">不具有角色分配保存权限的用户显示此内容 <br/></shiro:lacksPermission> </body> </html>
启动后在页面上输入:http://localhost:8080/boot/会跳转到login登录页面:
登录后会跳转到welcome页面,该页面使用了shiro标签,会进行权限认证: