springboot 集成 shiro ehcache

一、我的项目是多模块,这里shiro单独一个模块,下面的三个有用到的类

二、添加依赖包

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>

三、新建ShiroConfiguration.java(基本网上copy,自己根据需要修改了点东西)

@Configuration
public class ShiroConfiguration {
    private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);
    @Bean
    public EhCacheManager getEhCacheManager() {
        EhCacheManager em = new EhCacheManager();
        em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
        return em;
    }
    @Bean(name = "myShiroRealm")
    public MyShiroRealm myShiroRealm(EhCacheManager cacheManager) {
        MyShiroRealm realm = new MyShiroRealm();
        realm.setCacheManager(cacheManager);
        return realm;
    }
    /**
     * 注册DelegatingFilterProxy(Shiro)
     * 集成Shiro有2种方法:
     * 1. 按这个方法自己组装一个FilterRegistrationBean(这种方法更为灵活,可以自己定义UrlPattern,
     * 在项目使用中你可能会因为一些很但疼的问题最后采用它, 想使用它你可能需要看官网或者已经很了解Shiro的处理原理了)
     * 2. 直接使用ShiroFilterFactoryBean(这种方法比较简单,其内部对ShiroFilter做了组装工作,无法自己定义UrlPattern,
     * 默认拦截 /*)
     */
//  @Bean
//  public FilterRegistrationBean filterRegistrationBean() {
//      FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
//      filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
//      //  该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
//      filterRegistration.addInitParameter("targetFilterLifecycle", "true");
//      filterRegistration.setEnabled(true);
//      filterRegistration.addUrlPatterns("/*");// 可以自己灵活的定义很多,避免一些根本不需要被Shiro处理的请求被包含进来
//      return filterRegistration;
//  }

    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
        daap.setProxyTargetClass(true);
        return daap;
    }
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(MyShiroRealm myShiroRealm) {
        DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
        dwsm.setRealm(myShiroRealm);
//      <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
        dwsm.setCacheManager(getEhCacheManager());
        return dwsm;
    }
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
        aasa.setSecurityManager(securityManager);
        return aasa;
    }
    /**
     * 加载shiroFilter权限控制规则(从数据库读取然后配置)
     */
    private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){/*, UserService userService*/
        /////////////////////// 下面这些规则配置最好配置到配置文件中 ///////////////////////
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        //自定义拦截器 验证码使用
        //Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        //filters.put("captchaVaildate", new CaptchaValidateFilter());
        //filters.put("authc111", new MyFormAuthenticationFilter());
        // authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
        // anon:它对应的过滤器里面是空的,什么都没做
        logger.info("##################从数据库读取权限规则,加载到shiroFilter中##################");
        //使用自定义拦截器
        filterChainDefinitionMap.put("/sys/logining", "captchaVaildate");
        //filterChainDefinitionMap.put("/sys/main", "authc,roles[admin]");//这里跳转方法时候会需要权限校验
        // filterChainDefinitionMap.put("/sysUser/queryUser", "authc,roles[admin]");//这里跳转方法时候会需要权限校验(是否有角色),无权限时候跳转/sys/403
        filterChainDefinitionMap.put("/sys/main", "authc");
        filterChainDefinitionMap.put("/sys/403", "anon");
        filterChainDefinitionMap.put("/sys/logout", "anon");
        filterChainDefinitionMap.put("/sysUser/**", "authc");
        filterChainDefinitionMap.put("/**", "anon");//anon 可以理解为不拦截
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    }

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/sys/login");//未登录时候跳转
        // 登录成功后要跳转的连接
        shiroFilterFactoryBean.setSuccessUrl("/sys/main");
        shiroFilterFactoryBean.setUnauthorizedUrl("/sys/403");//无权限时候跳转
        loadShiroFilterChain(shiroFilterFactoryBean);/*, userService*/
        return shiroFilterFactoryBean;
    }

}

四、新建 MyShiroRealm.java

public class MyShiroRealm extends AuthorizingRealm{
    private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
    @Autowired
    private ISysUserService iSysUserService;
    @Autowired
    private SysUserMapper sysUserMapper;
    /**
     * 权限认证,为当前登录的Subject授予角色和权限
     * 本例中该方法的调用时机为需授权资源被访问时
     * 并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
     * 如果连续访问同一个URL(比如刷新),该方法不会被重复调用,Shiro有一个时间间隔(也就是cache时间,在ehcache-shiro.xml中配置),超过这个时间间隔再刷新页面,该方法会被执行
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("##################执行Shiro权限认证1111##################");
        //获取当前登录输入的用户名,等价于(String) principalCollection.fromRealm(getName()).iterator().next();
        String loginName = (String)super.getAvailablePrincipal(principalCollection);
        //到数据库查是否有此对象
        QueryWrapper<SysUser> wrapper = new QueryWrapper<SysUser>();
        wrapper.eq("user_name",loginName);
        SysUser sysUser=sysUserMapper.selectOne(wrapper);// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        if(sysUser!=null){
            //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
            SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
            //用户的角色集合
           // info.setRoles(user.getSysRolesName());
            Set<String> roles=new HashSet<String>();
            List<Map<String,Object>> mapList=iSysUserService.selectUserRoleById(sysUser.getId());
            for (Map<String, Object> map : mapList) {
                for (Map.Entry<String, Object> m : map.entrySet()) {
                    roles.add(m.getValue().toString());
                }
            }
            info.addRoles(roles); //角色 
            return info;
        }
        // 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedUrl指定的地址
        return null;
    }

    /**
     * 登录认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {
        //UsernamePasswordToken对象用来存放提交的登录信息
        UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
        logger.info("验证当前Subject时获取到token为:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
        //查出是否有此用户
      //  User user=userDao.findByName(token.getUsername());
        QueryWrapper<SysUser> wrapper = new QueryWrapper<SysUser>();
        wrapper.eq("user_name",token.getUsername());
        SysUser sysUser=sysUserMapper.selectOne(wrapper);
        if(sysUser!=null){
            // 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
            return new SimpleAuthenticationInfo(sysUser.getUserName(), sysUser.getPassword(), getName());
        }
        return null;
    }
}

五、新建MShiroFilterFactoryBean.java ,继承 ShiroFilterFactoryBean 处理拦截资源文件问题。

public class MShiroFilterFactoryBean extends ShiroFilterFactoryBean {
    // 对ShiroFilter来说,需要直接忽略的请求
    private Set<String> ignoreExt;

    public MShiroFilterFactoryBean() {
        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) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }

        FilterChainManager manager = createFilterChainManager();

        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

        return new MSpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }

    private final class MSpringShiroFilter extends AbstractShiroFilter {

        protected MSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
            super();
            if (webSecurityManager == null) {
                throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
            }
            setSecurityManager(webSecurityManager);
            if (resolver != null) {
                setFilterChainResolver(resolver);
            }
        }
        @Override
        protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse,
                                        FilterChain chain) throws ServletException, IOException {
            HttpServletRequest request = (HttpServletRequest)servletRequest;
            String str = request.getRequestURI().toLowerCase();
            // 因为ShiroFilter 拦截所有请求(在上面我们配置了urlPattern 为 * ,当然你也可以在那里精确的添加要处理的路径,这样就不需要这个类了),而在每次请求里面都做了session的读取和更新访问时间等操作,这样在集群部署session共享的情况下,数量级的加大了处理量负载。
            // 所以我们这里将一些能忽略的请求忽略掉。
            // 当然如果你的集群系统使用了动静分离处理,静态资料的请求不会到Filter这个层面,便可以忽略。
            boolean flag = true;
            int idx = 0;
            if(( idx = str.indexOf(".")) > 0){
                str = str.substring(idx);
                if(ignoreExt.contains(str.toLowerCase()))
                    flag = false;
            }
            if(flag){
                super.doFilterInternal(servletRequest, servletResponse, chain);
            }else{
                chain.doFilter(servletRequest, servletResponse);
            }
        }
    }
}

六、新建ehcache-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />
</ehcache>

七、写登录退出方法

@RequestMapping(value="logining")
@ResponseBody
public JSONObject login(HttpServletRequest request, Model model, HttpServletResponse response, @Valid SysUser user, BindingResult bindingResult, RedirectAttributes redirectAttributes){
    JSONObject json=new JSONObject();
    model.addAttribute("kry","ssss");
    if(bindingResult.hasErrors()){
        json.put("flag","faliure");
        return json;
    }
    String userName = user.getUserName();
    //此处表单提交一个是否rememberMe单选框,根据值request.getParameter("rememberMe")来判断是否添加“记住”功能
    //UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPwd(),true);
    UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(),Md5Utils.md5(user.getPassword()));
    //获取当前的Subject
    Subject currentUser = SecurityUtils.getSubject();
    try {
        //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
        //每个Realm都能在必要时对提交的AuthenticationTokens作出反应
        //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
        logger.info("对用户[" + userName + "]进行登录验证..验证开始");
        currentUser.login(token);
        Session session = currentUser.getSession(true);
        //主机IP
        System.out.println("host:"+session.getHost());
        //session超时时间
        session.setTimeout(1800000);//30分钟
        // session.setTimeout(5000);//
        System.out.println("timeout:"+session.getTimeout());
        logger.info("对用户[" + userName + "]进行登录验证..验证通过");
    }catch(UnknownAccountException uae){
        logger.info("对用户[" + userName + "]进行登录验证..验证未通过,未知账户");
        redirectAttributes.addFlashAttribute("message", "未知账户");
        json.put("message","未知账户");
    }catch(IncorrectCredentialsException ice){
        logger.info("对用户[" + userName + "]进行登录验证..验证未通过,错误的凭证");
        redirectAttributes.addFlashAttribute("message", "密码不正确");
        json.put("message","密码不正确");
    }catch(LockedAccountException lae){
        logger.info("对用户[" + userName + "]进行登录验证..验证未通过,账户已锁定");
        redirectAttributes.addFlashAttribute("message", "账户已锁定");
        json.put("message","账户已锁定");
    }catch(ExcessiveAttemptsException eae){
        logger.info("对用户[" + userName + "]进行登录验证..验证未通过,错误次数过多");
        redirectAttributes.addFlashAttribute("message", "用户名或密码错误次数过多");
        json.put("message","用户名或密码错误次数过多");
    }catch(AuthenticationException ae){
        //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
        logger.info("对用户[" + userName + "]进行登录验证..验证未通过,堆栈轨迹如下");
        ae.printStackTrace();
        redirectAttributes.addFlashAttribute("message", "用户名或密码不正确");
        json.put("message","用户名或密码不正确");
    }
   
    //验证是否登录成功
    if(currentUser.isAuthenticated()){
        logger.info("用户[" + userName + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");
        if (currentUser.hasRole("admin")) {
            logger.info("-----------admin---------");
        }
        if (currentUser.hasRole("user")) {
            logger.info("-----------user---------");
        }
        json.put("flag","success");
        return json;
    }else{
        token.clear();
        json.put("flag","failure");
        return json;
    }
}
@RequestMapping(value="/logout",method=RequestMethod.GET)
public String logout(RedirectAttributes redirectAttributes ){
    //使用权限管理工具进行用户的退出,跳出登录,给出提示信息
    SecurityUtils.getSubject().logout();
    redirectAttributes.addFlashAttribute("message", "您已安全退出");
    logger.info("------您已安全退出-------");
    return "redirect:/sys/login";
}

@RequestMapping("/403")
public String unauthorizedRole(){
    logger.info("------没有权限-------");
    return "sys/403";
}

到这里就可以用了(在配置过程中遇到许多问题)

猜你喜欢

转载自blog.csdn.net/sinat_34338162/article/details/84256365