springboot学习笔记:11.springboot+shiro+mysql+mybatis(通用mapper)+freemarker+ztree+layui实现通用的java后台管理系统(权限管理+用户管理+菜单管理)

安稳与你 提交于 2020-02-11 15:38:20

一.前言

今天我们用springboot集成shiro框架,实现一个可以通用的后台管理系统;包括用户管理,角色管理,菜单管理三大系统常用管理模块;

二.数据库表准备:

要想实现用户管理+角色管理+菜单管理三大模块,基本上我们常用的解决方案就是如下五个表(sql脚本在最后):

三.集成shiro和配置

1.添加pom依赖。

复制代码
<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
复制代码

 

2.编辑shiro配置类:ShiroConfig.java

复制代码
package com.zjt.config;

import com.zjt.realm.MyRealm;
import org.apache.shiro.mgt.SecurityManager;
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.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* @Author: Zhaojiatao
* @Description: Shiro配置类
* @Date: Created in 2018/2/8 13:29
* @param 
*/
@Configuration
public class ShiroConfig {

    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     *
     * Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
     * 3、部分过滤器可指定参数,如perms,roles
     *
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        //shiroFilterFactoryBean.setLoginUrl("/login.ftl");

        //配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        shiroFilterFactoryBean.setLoginUrl("/tologin");
        shiroFilterFactoryBean.setUnauthorizedUrl("/tologin");

        // 拦截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        //配置记住我或认证通过可以访问的地址(配置不会被拦截的链接 顺序判断)
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/user/login", "anon");
        filterChainDefinitionMap.put("/drawImage", "anon");

        // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/admin/user/logout", "logout");


        // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
}

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(myRealm());

        //注入记住我管理器;
        securityManager.setRememberMeManager(rememberMeManager());


        return securityManager;
    }

    /**
     * 身份认证realm; (这个需要自己写,账号密码校验;权限等)
     * 
     * @return
     */
    @Bean
    public MyRealm myRealm() {
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }

    /**
     * Shiro生命周期处理器
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }
    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     * 不要使用 DefaultAdvisorAutoProxyCreator 会出现二次代理的问题,这里不详述
     * @return
     */
   /* @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }*/
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }





    /**
     * cookie对象;
     * 记住密码实现起来也是比较简单的,主要看下是如何实现的。
     * @return
     */
    @Bean
    public SimpleCookie rememberMeCookie(){
        System.out.println("ShiroConfiguration.rememberMeCookie()");
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //<!-- 记住我cookie生效时间30天 ,单位秒;-->
        simpleCookie.setMaxAge(259200);
        return simpleCookie;
    }

    /**
     * cookie管理对象;
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        System.out.println("ShiroConfiguration.rememberMeManager()");
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }





}
复制代码

 

3.实现自定义MyRealm.java

复制代码
package com.zjt.realm;

import com.zjt.entity.Tmenu;
import com.zjt.entity.Trole;
import com.zjt.entity.Tuser;
import com.zjt.mapper.TmenuMapper;
import com.zjt.mapper.TroleMapper;
import com.zjt.mapper.TuserMapper;
import com.zjt.mapper.TuserroleMapper;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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 tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 自定义Realm
 * @author zjt
 *
 */
public class MyRealm extends AuthorizingRealm{

    @Resource
    private TuserMapper tuserMapper;
    
    @Resource
    private TroleMapper troleMapper;

    @Resource
    private TuserroleMapper tuserroleMapper;
    
    @Resource
    private TmenuMapper tmenuMapper;

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String userName=(String) SecurityUtils.getSubject().getPrincipal();

        //User user=userRepository.findByUserName(userName);
        //根据用户名查询出用户记录
        Example tuserExample=new Example(Tuser.class);
        tuserExample.or().andEqualTo("userName",userName);
        Tuser user=tuserMapper.selectByExample(tuserExample).get(0);


        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();

        //List<Role> roleList=roleRepository.findByUserId(user.getId());
        List<Trole> roleList = troleMapper.selectRolesByUserId(user.getId());

        Set<String> roles=new HashSet<String>();
        if(roleList.size()>0){
            for(Trole role:roleList){
                roles.add(role.getName());
                //List<Tmenu> menuList=menuRepository.findByRoleId(role.getId());
                //根据角色id查询所有资源
                List<Tmenu> menuList=tmenuMapper.selectMenusByRoleId(role.getId());
                for(Tmenu menu:menuList){
                    info.addStringPermission(menu.getName()); // 添加权限
                }
            }
        }
        info.setRoles(roles);
        return info;
    }

    /**
     * 权限认证
                */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            String userName=(String)token.getPrincipal();
            //User user=userRepository.findByUserName(userName);
            Example tuserExample=new Example(Tuser.class);
            tuserExample.or().andEqualTo("userName",userName);
            Tuser user=tuserMapper.selectByExample(tuserExample).get(0);
            if(user!=null){
                AuthenticationInfo authcInfo=new SimpleAuthenticationInfo(user.getUserName(),user.getPassword(),"xxx");
                return authcInfo;
            }else{
                return null;
            }
    }

}
复制代码

 

 

4.登录、退出、权限限制

登录:subject.login(token);

退出:SecurityUtils.getSubject().logout();

在方法前使用shiro注解实现权限校验,如:@RequiresPermissions(value = {"用户管理"}) 表示当前用户必须拥有用户管理的权限;

四、前端实现及效果展示

1、登录

请求http://localhost:8080/blogmanager/

被shiro拦截后自动跳转到登录界面;

项目地址可以在配置文件中配置:

 

源码:src\main\resources\templates\login.ftl

用户名:admin

密码:1

 

 

 2、系统管理-菜单管理

菜单管理页面源码:src\main\resources\templates\power\menu.ftl

里面使用了ztree实现的菜单的新建、编辑、删除;

菜单管理的后台接口:com.zjt.web.MenuController.java

 

注意一级菜单在顶部显示,且一级菜单名不可为纯数字;

二级三级菜单在左侧显示,且最多只能到三级菜单;

 

 3、系统管理-角色管理

src\main\resources\templates\power\role.ftl

com.zjt.web.RoleAdminController.java

页面使用了jqgrid表格插件;

并可以设置每个角色对应的菜单权限:

4、系统管理-用户管理

 src\main\resources\templates\power\user.ftl

 com.zjt.web.UserAdminController.java

 

选择行后可以设置角色:

 

 五、后记

 本后台管理系统可作为通用的后台管理系统,她简单纯净;内置完善的菜单管理+角色管理+用户管理;拿来即用;

使用技术涉及:

springboot+springmvc+mysql+mybatis+通用mapper+分页插件+shiro+freemarker+layui+ztree

 其中layui模板使用的是layuicms2.0

 

本项目源码:

 https://github.com/zhaojiatao/springboot-zjt-chapter10-springboot-mysql-mybatis-shiro-freemarker-layui.git

 sql脚本含在项目sql文件夹中

 

项目访问地址和端口在配置文件中:application-dev.properties

今天我们用springboot集成shiro框架,实现一个可以通用的后台管理系统;包括用户管理,角色管理,菜单管理三大系统常用管理模块;

二.数据库表准备:

要想实现用户管理+角色管理+菜单管理三大模块,基本上我们常用的解决方案就是如下五个表(sql脚本在最后):

三.集成shiro和配置

1.添加pom依赖。

复制代码
<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
复制代码

 

2.编辑shiro配置类:ShiroConfig.java

复制代码
package com.zjt.config;

import com.zjt.realm.MyRealm;
import org.apache.shiro.mgt.SecurityManager;
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.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* @Author: Zhaojiatao
* @Description: Shiro配置类
* @Date: Created in 2018/2/8 13:29
* @param 
*/
@Configuration
public class ShiroConfig {

    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     *
     * Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
     * 3、部分过滤器可指定参数,如perms,roles
     *
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        //shiroFilterFactoryBean.setLoginUrl("/login.ftl");

        //配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        shiroFilterFactoryBean.setLoginUrl("/tologin");
        shiroFilterFactoryBean.setUnauthorizedUrl("/tologin");

        // 拦截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        //配置记住我或认证通过可以访问的地址(配置不会被拦截的链接 顺序判断)
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/user/login", "anon");
        filterChainDefinitionMap.put("/drawImage", "anon");

        // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/admin/user/logout", "logout");


        // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
}

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(myRealm());

        //注入记住我管理器;
        securityManager.setRememberMeManager(rememberMeManager());


        return securityManager;
    }

    /**
     * 身份认证realm; (这个需要自己写,账号密码校验;权限等)
     * 
     * @return
     */
    @Bean
    public MyRealm myRealm() {
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }

    /**
     * Shiro生命周期处理器
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }
    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     * 不要使用 DefaultAdvisorAutoProxyCreator 会出现二次代理的问题,这里不详述
     * @return
     */
   /* @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }*/
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }





    /**
     * cookie对象;
     * 记住密码实现起来也是比较简单的,主要看下是如何实现的。
     * @return
     */
    @Bean
    public SimpleCookie rememberMeCookie(){
        System.out.println("ShiroConfiguration.rememberMeCookie()");
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //<!-- 记住我cookie生效时间30天 ,单位秒;-->
        simpleCookie.setMaxAge(259200);
        return simpleCookie;
    }

    /**
     * cookie管理对象;
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        System.out.println("ShiroConfiguration.rememberMeManager()");
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }





}
复制代码

 

3.实现自定义MyRealm.java

复制代码
package com.zjt.realm;

import com.zjt.entity.Tmenu;
import com.zjt.entity.Trole;
import com.zjt.entity.Tuser;
import com.zjt.mapper.TmenuMapper;
import com.zjt.mapper.TroleMapper;
import com.zjt.mapper.TuserMapper;
import com.zjt.mapper.TuserroleMapper;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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 tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 自定义Realm
 * @author zjt
 *
 */
public class MyRealm extends AuthorizingRealm{

    @Resource
    private TuserMapper tuserMapper;
    
    @Resource
    private TroleMapper troleMapper;

    @Resource
    private TuserroleMapper tuserroleMapper;
    
    @Resource
    private TmenuMapper tmenuMapper;

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String userName=(String) SecurityUtils.getSubject().getPrincipal();

        //User user=userRepository.findByUserName(userName);
        //根据用户名查询出用户记录
        Example tuserExample=new Example(Tuser.class);
        tuserExample.or().andEqualTo("userName",userName);
        Tuser user=tuserMapper.selectByExample(tuserExample).get(0);


        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();

        //List<Role> roleList=roleRepository.findByUserId(user.getId());
        List<Trole> roleList = troleMapper.selectRolesByUserId(user.getId());

        Set<String> roles=new HashSet<String>();
        if(roleList.size()>0){
            for(Trole role:roleList){
                roles.add(role.getName());
                //List<Tmenu> menuList=menuRepository.findByRoleId(role.getId());
                //根据角色id查询所有资源
                List<Tmenu> menuList=tmenuMapper.selectMenusByRoleId(role.getId());
                for(Tmenu menu:menuList){
                    info.addStringPermission(menu.getName()); // 添加权限
                }
            }
        }
        info.setRoles(roles);
        return info;
    }

    /**
     * 权限认证
                */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            String userName=(String)token.getPrincipal();
            //User user=userRepository.findByUserName(userName);
            Example tuserExample=new Example(Tuser.class);
            tuserExample.or().andEqualTo("userName",userName);
            Tuser user=tuserMapper.selectByExample(tuserExample).get(0);
            if(user!=null){
                AuthenticationInfo authcInfo=new SimpleAuthenticationInfo(user.getUserName(),user.getPassword(),"xxx");
                return authcInfo;
            }else{
                return null;
            }
    }

}
复制代码

 

 

4.登录、退出、权限限制

登录:subject.login(token);

退出:SecurityUtils.getSubject().logout();

在方法前使用shiro注解实现权限校验,如:@RequiresPermissions(value = {"用户管理"}) 表示当前用户必须拥有用户管理的权限;

四、前端实现及效果展示

1、登录

请求http://localhost:8080/blogmanager/

被shiro拦截后自动跳转到登录界面;

项目地址可以在配置文件中配置:

 

源码:src\main\resources\templates\login.ftl

用户名:admin

密码:1

 

 

 2、系统管理-菜单管理

菜单管理页面源码:src\main\resources\templates\power\menu.ftl

里面使用了ztree实现的菜单的新建、编辑、删除;

菜单管理的后台接口:com.zjt.web.MenuController.java

 

注意一级菜单在顶部显示,且一级菜单名不可为纯数字;

二级三级菜单在左侧显示,且最多只能到三级菜单;

 

 3、系统管理-角色管理

src\main\resources\templates\power\role.ftl

com.zjt.web.RoleAdminController.java

页面使用了jqgrid表格插件;

并可以设置每个角色对应的菜单权限:

4、系统管理-用户管理

 src\main\resources\templates\power\user.ftl

 com.zjt.web.UserAdminController.java

 

选择行后可以设置角色:

 

 五、后记

 本后台管理系统可作为通用的后台管理系统,她简单纯净;内置完善的菜单管理+角色管理+用户管理;拿来即用;

使用技术涉及:

springboot+springmvc+mysql+mybatis+通用mapper+分页插件+shiro+freemarker+layui+ztree

 其中layui模板使用的是layuicms2.0

 

本项目源码:

 https://github.com/zhaojiatao/springboot-zjt-chapter10-springboot-mysql-mybatis-shiro-freemarker-layui.git

 sql脚本含在项目sql文件夹中

 

项目访问地址和端口在配置文件中:application-dev.properties

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!