SpringBoot学习:整合shiro(身份认证和权限认证),使用EhCache缓存

1、在pom中引入依赖jar包css

 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-->  

 

2、shiro配置类:java

  ShiroConfiguration:web

  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      一、一个URL能够配置多个Filter,使用逗号分隔
 30      二、当设置多个过滤器时,所有验证经过,才视为经过
 31      三、部分过滤器可指定参数,如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 :spring

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:apache

 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 }