一、在pom中引入依赖jar包
1 <properties> 2 <shiro.version>1.3.2</shiro.version> 3 </properties> 4 5 <!--shiro start--> 6 <dependency> 7 <groupId>org.apache.shiro</groupId> 8 <artifactId>shiro-core</artifactId> 9 <version>${shiro.version}</version> 10 </dependency> 11 <dependency> 12 <groupId>org.apache.shiro</groupId> 13 <artifactId>shiro-web</artifactId> 14 <version>${shiro.version}</version> 15 </dependency> 16 <dependency> 17 <groupId>org.apache.shiro</groupId> 18 <artifactId>shiro-ehcache</artifactId> 19 <version>${shiro.version}</version> 20 </dependency> 21 <dependency> 22 <groupId>org.apache.shiro</groupId> 23 <artifactId>shiro-spring</artifactId> 24 <version>${shiro.version}</version> 25 </dependency> 26 <!--shiro end-->
二、shiro配置类:
ShiroConfiguration:
1 package com.example.demo; 2 3 import org.apache.log4j.Logger; 4 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Configuration; 7 8 import java.util.LinkedHashMap; 9 import java.util.Map; 10 11 /** 12 * Shiro 配置 13 * Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 14 * 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。 15 * Created by sun on 2017-4-2. 16 */ 17 @Configuration 18 @EnableTransactionManagement 19 public class ShiroConfiguration{ 20 21 private final Logger logger = Logger.getLogger(ShiroConfiguration.class); 22 23 /** 24 * ShiroFilterFactoryBean 处理拦截资源文件问题。 25 * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在 26 * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager 27 * 28 Filter Chain定义说明 29 1、一个URL可以配置多个Filter,使用逗号分隔 30 2、当设置多个过滤器时,全部验证通过,才视为通过 31 3、部分过滤器可指定参数,如perms,roles 32 * 33 */ 34 @Bean 35 public EhCacheManager getEhCacheManager(){ 36 EhCacheManager ehcacheManager = new EhCacheManager(); 37 ehcacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml"); 38 return ehcacheManager; 39 } 40 41 //配置shiro仓库 42 @Bean(name = "myShiroRealm") 43 public MyShiroRealm myShiroRealm(EhCacheManager ehCacheManager){ 44 MyShiroRealm realm = new MyShiroRealm(); 45 realm.setCacheManager(ehCacheManager); 46 return realm; 47 } 48 49 //把shiro生命周期交给spring boot管理 50 @Bean(name = "lifecycleBeanPostProcessor") 51 public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ 52 return new LifecycleBeanPostProcessor(); 53 } 54 55 //DefaultAdvisorAutoProxyCreator实现Spring自动代理 56 @Bean 57 public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ 58 DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); 59 creator.setProxyTargetClass(true); 60 return creator; 61 } 62 63 //权限认证信息 64 @Bean(name = "securityManager") 65 public DefaultWebSecurityManager defaultWebSecurityManager(MyShiroRealm realm){ 66 System.out.println("shiro~~~~~~~~启动"); 67 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 68 //设置realm 69 securityManager.setRealm(realm); 70 securityManager.setCacheManager(getEhCacheManager()); 71 return securityManager; 72 } 73 74 @Bean 75 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){ 76 AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); 77 advisor.setSecurityManager(securityManager); 78 return advisor; 79 } 80 81 //shiro核心拦截器 82 @Bean(name = "shiroFilter") 83 public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){ 84 ShiroFilterFactoryBean factoryBean = new MyShiroFilterFactoryBean(); 85 factoryBean.setSecurityManager(securityManager); 86 // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 87 factoryBean.setLoginUrl("/login"); 88 // 登录成功后要跳转的连接 89 factoryBean.setSuccessUrl("/welcome"); 90 factoryBean.setUnauthorizedUrl("/403"); 91 loadShiroFilterChain(factoryBean); 92 logger.info("shiro拦截器工厂类注入成功"); 93 return factoryBean; 94 } 95 96 //加载ShiroFilter权限控制规则 97 private void loadShiroFilterChain(ShiroFilterFactoryBean factoryBean) { 98 /**下面这些规则配置最好配置到配置文件中*/ 99 Map<String, String> filterChainMap = new LinkedHashMap<String, String>(); 100 // authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器 101 // anon:它对应的过滤器里面是空的,什么都没做,可以理解为不拦截 102 //authc:所有url都必须认证通过才可以访问; anon:所有url都可以匿名访问 103 filterChainMap.put("/permission/userInsert", "anon"); 104 filterChainMap.put("/error", "anon"); 105 filterChainMap.put("/tUser/insert","anon"); 106 filterChainMap.put("/**", "authc"); 107 108 factoryBean.setFilterChainDefinitionMap(filterChainMap); 109 } 110 111 /* 112 1.LifecycleBeanPostProcessor,这是个DestructionAwareBeanPostProcessor的子类,负责org.apache.shiro.util.Initializable类型bean的生命周期的,初始化和销毁。主要是AuthorizingRealm类的子类,以及EhCacheManager类。 113 2.HashedCredentialsMatcher,这个类是为了对密码进行编码的,防止密码在数据库里明码保存,当然在登陆认证的生活,这个类也负责对form里输入的密码进行编码。 114 3.ShiroRealm,这是个自定义的认证类,继承自AuthorizingRealm,负责用户的认证和权限的处理,可以参考JdbcRealm的实现。 115 4.EhCacheManager,缓存管理,用户登陆成功后,把用户信息和权限信息缓存起来,然后每次用户请求时,放入用户的session中,如果不设置这个bean,每个请求都会查询一次数据库。 116 5.SecurityManager,权限管理,这个类组合了登陆,登出,权限,session的处理,是个比较重要的类。 117 6.ShiroFilterFactoryBean,是个factorybean,为了生成ShiroFilter。它主要保持了三项数据,securityManager,filters,filterChainDefinitionManager。 118 7.DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。 119 8.AuthorizationAttributeSourceAdvisor,shiro里实现的Advisor类,内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。 120 */ 121 }
MyShiroRealm :
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; } }
MyShiroFilterFactoryBean:
1 package com.sun.configuration; 2 3 import org.apache.shiro.mgt.SecurityManager; 4 import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 5 import org.apache.shiro.web.filter.mgt.FilterChainManager; 6 import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; 7 import org.apache.shiro.web.mgt.WebSecurityManager; 8 import org.apache.shiro.web.servlet.AbstractShiroFilter; 9 import org.springframework.beans.factory.BeanInitializationException; 10 11 import javax.servlet.FilterChain; 12 import javax.servlet.ServletException; 13 import javax.servlet.ServletRequest; 14 import javax.servlet.ServletResponse; 15 import javax.servlet.http.HttpServletRequest; 16 import java.io.IOException; 17 import java.util.HashSet; 18 import java.util.Set; 19 20 /** 21 * Created by sun on 2017-4-2. 22 */ 23 public class MyShiroFilterFactoryBean extends ShiroFilterFactoryBean { 24 // ShiroFilter将直接忽略的请求 25 private Set<String> ignoreExt; 26 27 public MyShiroFilterFactoryBean(){ 28 super(); 29 ignoreExt = new HashSet<String>(); 30 ignoreExt.add(".jpg"); 31 ignoreExt.add(".png"); 32 ignoreExt.add(".gif"); 33 ignoreExt.add(".bmp"); 34 ignoreExt.add(".js"); 35 ignoreExt.add(".css"); 36 } 37 /** 38 * 启动时加载 39 */ 40 @Override 41 protected AbstractShiroFilter createInstance() throws Exception { 42 SecurityManager securityManager = getSecurityManager(); 43 if (securityManager == null){ 44 throw new BeanInitializationException("SecurityManager property must be set."); 45 } 46 47 if (!(securityManager instanceof WebSecurityManager)){ 48 throw new BeanInitializationException("The security manager does not implement the WebSecurityManager interface."); 49 } 50 51 PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); 52 FilterChainManager chainManager = createFilterChainManager(); 53 chainResolver.setFilterChainManager(chainManager); 54 return new MySpringShiroFilter((WebSecurityManager)securityManager, chainResolver); 55 } 56 57 /** 58 * 启动时加载 59 */ 60 private class MySpringShiroFilter extends AbstractShiroFilter { 61 public MySpringShiroFilter( 62 WebSecurityManager securityManager, PathMatchingFilterChainResolver chainResolver) { 63 super(); 64 if (securityManager == null){ 65 throw new IllegalArgumentException("WebSecurityManager property cannot be null."); 66 } 67 setSecurityManager(securityManager); 68 if (chainResolver != null){ 69 setFilterChainResolver(chainResolver); 70 } 71 } 72 /** 73 * 页面上传输的url先进入此方法验证 74 */ 75 @Override 76 protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, 77 FilterChain chain) 78 throws ServletException, IOException { 79 HttpServletRequest request = (HttpServletRequest)servletRequest; 80 String str = request.getRequestURI().toLowerCase(); 81 boolean flag = true; 82 int idx = 0; 83 if ((idx = str.lastIndexOf(".")) > 0){ 84 str = str.substring(idx); 85 if (ignoreExt.contains(str.toLowerCase())){ 86 flag = false; 87 } 88 } 89 if (flag){ 90 super.doFilterInternal(servletRequest, servletResponse, chain); 91 } else { 92 chain.doFilter(servletRequest, servletResponse); 93 } 94 } 95 } 96 }
ehcache-shiro.xml:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <ehcache updateCheck="false" name="shiroCache"> 3 <!-- 4 name:缓存名称。 5 maxElementsInMemory:缓存最大数目 6 maxElementsOnDisk:硬盘最大缓存个数。 7 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 8 overflowToDisk:是否保存到磁盘,当系统当机时 9 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 10 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 11 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. 12 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 13 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 14 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 15 clearOnFlush:内存数量最大时是否清除。 16 memoryStoreEvictionPolicy: 17 Ehcache的三种清空策略; 18 FIFO,first in first out,这个是大家最熟的,先进先出。 19 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。 20 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 21 --> 22 <defaultCache 23 maxElementsInMemory="10000" 24 eternal="false" 25 timeToIdleSeconds="120" 26 timeToLiveSeconds="120" 27 overflowToDisk="false" 28 diskPersistent="false" 29 diskExpiryThreadIntervalSeconds="120" 30 /> 31 <!-- 登录记录缓存锁定10分钟 --> 32 <cache name="passwordRetryCache" 33 maxEntriesLocalHeap="2000" 34 eternal="false" 35 timeToIdleSeconds="3600" 36 timeToLiveSeconds="0" 37 overflowToDisk="false" 38 statistics="true"> 39 </cache> 40 </ehcache>
登录的controller类:
1 package com.sun.permission.controller; 2 3 import com.sun.permission.model.User; 4 import com.sun.permission.service.PermissionService; 5 import com.sun.util.CommonUtils; 6 import org.apache.commons.lang3.StringUtils; 7 import org.apache.log4j.Logger; 8 import org.apache.shiro.SecurityUtils; 9 import org.apache.shiro.authc.*; 10 import org.apache.shiro.session.Session; 11 import org.apache.shiro.subject.Subject; 12 import org.springframework.beans.factory.annotation.Autowired; 13 import org.springframework.stereotype.Controller; 14 import org.springframework.validation.BindingResult; 15 import org.springframework.web.bind.annotation.RequestMapping; 16 import org.springframework.web.bind.annotation.RequestMethod; 17 import org.springframework.web.servlet.ModelAndView; 18 import org.springframework.web.servlet.mvc.support.RedirectAttributes; 19 20 import javax.validation.Valid; 21 22 /** 23 * Created by sun on 2017-4-2. 24 */ 25 @Controller 26 public class LoginController { 27 private static final Logger logger = Logger.getLogger(LoginController.class); 28 @Autowired 29 private PermissionService permissionService; 30 31 @RequestMapping(value="/login",method= RequestMethod.GET) 32 public ModelAndView loginForm(){ 33 ModelAndView model = new ModelAndView(); 34 model.addObject("user", new User()); 35 model.setViewName("login"); 36 return model; 37 } 38 39 @RequestMapping(value="/login",method=RequestMethod.POST) 40 public String login(@Valid User user, BindingResult bindingResult, RedirectAttributes redirectAttributes){ 41 if(bindingResult.hasErrors()){ 42 return "redirect:login"; 43 } 44 String email = user.getEmail(); 45 if(StringUtils.isBlank(user.getEmail()) || StringUtils.isBlank(user.getPswd())){ 46 logger.info("用户名或密码为空! "); 47 redirectAttributes.addFlashAttribute("message", "用户名或密码为空!"); 48 return "redirect:login"; 49 } 50 //验证 51 UsernamePasswordToken token = new UsernamePasswordToken(user.getEmail(), user.getPswd()); 52 //获取当前的Subject 53 Subject currentUser = SecurityUtils.getSubject(); 54 try { 55 //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查 56 //每个Realm都能在必要时对提交的AuthenticationTokens作出反应 57 //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法 58 logger.info("对用户[" + email + "]进行登录验证..验证开始"); 59 currentUser.login(token); 60 logger.info("对用户[" + email + "]进行登录验证..验证通过"); 61 }catch(UnknownAccountException uae){ 62 logger.info("对用户[" + email + "]进行登录验证..验证未通过,未知账户"); 63 redirectAttributes.addFlashAttribute("message", "未知账户"); 64 }catch(IncorrectCredentialsException ice){ 65 logger.info("对用户[" + email + "]进行登录验证..验证未通过,错误的凭证"); 66 redirectAttributes.addFlashAttribute("message", "密码不正确"); 67 }catch(LockedAccountException lae){ 68 logger.info("对用户[" + email + "]进行登录验证..验证未通过,账户已锁定"); 69 redirectAttributes.addFlashAttribute("message", "账户已锁定"); 70 }catch(ExcessiveAttemptsException eae){ 71 logger.info("对用户[" + email + "]进行登录验证..验证未通过,错误次数大于5次,账户已锁定"); 72 redirectAttributes.addFlashAttribute("message", "用户名或密码错误次数大于5次,账户已锁定"); 73 }catch (DisabledAccountException sae){ 74 logger.info("对用户[" + email + "]进行登录验证..验证未通过,帐号已经禁止登录"); 75 redirectAttributes.addFlashAttribute("message", "帐号已经禁止登录"); 76 }catch(AuthenticationException ae){ 77 //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景 78 logger.info("对用户[" + email + "]进行登录验证..验证未通过,堆栈轨迹如下"); 79 ae.printStackTrace(); 80 redirectAttributes.addFlashAttribute("message", "用户名或密码不正确"); 81 } 82 //验证是否登录成功 83 if(currentUser.isAuthenticated()){ 84 logger.info("用户[" + email + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)"); 85 //把当前用户放入session 86 Session session = currentUser.getSession(); 87 User tUser = permissionService.findByUserEmail(email); 88 session.setAttribute("currentUser",tUser); 89 return "/welcome"; 90 }else{ 91 token.clear(); 92 return "redirect:login"; 93 } 94 } 95 96 @RequestMapping(value="/logout",method=RequestMethod.GET) 97 public String logout(RedirectAttributes redirectAttributes ){ 98 //使用权限管理工具进行用户的退出,跳出登录,给出提示信息 99 SecurityUtils.getSubject().logout(); 100 redirectAttributes.addFlashAttribute("message", "您已安全退出"); 101 return "redirect:login"; 102 } 103 104 @RequestMapping("/403") 105 public String unauthorizedRole(){ 106 logger.info("------没有权限-------"); 107 return "errorPermission"; 108 } 109 110 }