Shiro通过数据库配置后台管理项目权限

前言

SpringBoot整合Shiro配置后台管理项目权限,普遍的方式是通过添加shiro标签在html文件里面,然后判断当前用户是否拥有当前权限,来确认是否展示当前菜单,shiro标签类似如下:

<!--验证当前用户是否拥有指定权限。  -->
<a shiro:hasPermission="user:add" href="#" >add用户</a><!-- 拥有权限 -->
<!--与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。-->
<p shiro:lacksPermission="user:del"> 没有权限 </p>
<!--验证当前用户是否拥有以下所有权限。-->
<p shiro:hasAllPermissions="user:view, user:add"> 权限与判断 </p>

这种方式缺点是需要手写所有菜单标签,也要加上对应的权限标识,在菜单列表比较多的时候,index页面会显得很臃肿,而且自己也容易混淆其中的权限标识。上次看了同事管理权限的方式,感觉很精妙,他是通过在数据库里面创建菜单表,关联权限表用户表,然后在进入index页面的时候,通过freemarker模板引擎,循环创建菜单表,这样一来,进入index页面之后,只会显示当前用户拥有权限的菜单。而且这种方式,在搭建好了后台配置之后,只需要手动的在数据库里面写入菜单名,index页面就会直接显示,这样不容易搞混淆。

后台搭建

数据库表

1.user表

2.user_role表

3.role表

4.permission表

5.role_permission表

6.menu表

代码

1.Controller

    @GetMapping("/index")
    public String login(Model model) {
        //获取当前用户名得到菜单
        Subject subject = SecurityUtils.getSubject();
        if(!subject.isAuthenticated()) {
            return "/login";
        }
        //根据当前登录账号来获取当前当前账号所拥有权限的菜单列表
        String username = subject.getPrincipal().toString();
        List<Menus> menuTree = menuService.findMenuTreeByUsername(username);
        model.addAttribute("menuTree",menuTree);
        return "index";
    }

2.Service

public interface MenuService extends IService<Menu> {
    List<Menus> findMenuTreeByUsername(String username);
}

3.ServiceImpl

@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
    @Autowired
    private MenuMapper menuMapper;

    @Override
    public List<Menus> findMenuTreeByUsername(String username) {
        return menuMapper.findMenuTreeByUsername(username);
    }
}

4.Mapper

public interface MenuMapper extends BaseMapper<Menu> {
    /***
     * 根据用户名获取菜单树
     * @param username
     * @return
     */
    List<Menus> findMenuTreeByUsername(@Param("username")String username);
}

5.Xml

    <resultMap type="com.appointment.model.vo.Menus" id="MenuMap">
        <id property="menuId" column="id" jdbcType="BIGINT" />
        <result property="menuName" column="menu_name" jdbcType="VARCHAR"/>
        <result property="menuIcon" column="menu_icon" jdbcType="VARCHAR"/>
        <collection property="subMenus" ofType="com.appointment.model.vo.Menus">
            <id property="menuId" column="mid" jdbcType="BIGINT" />
            <result property="menuName" column="mname" jdbcType="VARCHAR"/>
            <result property="menuUrl" column="murl" jdbcType="VARCHAR"/>
        </collection>
    </resultMap>

    <select id="findMenuTreeByUsername" resultMap="MenuMap">
        select pm.id,pm.menu_name,pm.menu_icon,m.id mid,m.menu_name mname,m.menu_url murl
        from user u
        inner join user_role ur on u.id=ur.user_id
        inner join role role on ur.role_id=role.id
        inner join role_permission rp on role.id=rp.role_id
        inner join permission p on rp.permission_id=p.id
        inner join menu m on p.menu_id=m.id
        inner join menu pm on m.menu_id=pm.id
        where u.username=#{username}
    </select>

6.VO对象

@Data
public class Menus {
    private Long menuId;
    private String menuName;
    private String menuUrl;
    private String menuIcon;
    private List<Menus> subMenus;
}
前端页面生成菜单列表
//此处是通过freemarker模板引擎生成,用thymeleaf的话也是可以的
                <ul class="layui-nav layui-nav-tree" lay-shrink="all" id="LAY-system-side-menu" lay-filter="layadmin-system-side-menu">
                menuTree
                <#list menuTree as menus>
                <li class="layui-nav-item">
                    <a href="javascript:;"><i class="${menus.menuIcon}"></i>
                        ${menus.menuName}
                    </a>
                    <dl  class="layui-nav-child">
                        <#list menus.subMenus as s>
                        <dd data-name="${s.menuName}" >
                            <a lay-href="${request.contextPath}${s.menuUrl}">${s.menuName}</a>
                        </dd>
                        </#list><
                    /dl>
                </li>
                </#list>
            </ul>
Shiro配置

1.ShiroConfig添加权限标识
ShiroConfig文件里面,通过查询在数据库里配置的全选标识,给所有的需要权限的资源添加权限标识,同时,通过这种方式,直接省略了在每个接口上面添加@RequiresPermissions("user:add")类似的注解

    /**
     * 定义shiroFilter过滤器并注入securityManager
     * @param manager
     * @return
     */
    @Bean("shiroFilter")       //必须叫这个。
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置securityManager
        bean.setSecurityManager(manager);
        //设置登录页面
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");
        bean.setUnauthorizedUrl("/auth.html");
        //定义过滤器
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //配置记住我或认证通过可以访问的地址
        filterChainDefinitionMap.put("/index", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/swagger-resources", "anon");
        filterChainDefinitionMap.put("/swagger-resources/configuration/security", "anon");
        filterChainDefinitionMap.put("/swagger-resources/configuration/ui", "anon");
        filterChainDefinitionMap.put("/api/**", "anon");
        //通过查询在数据库里面的权限标识,循环给自己设定的字段添加标识权限
        List<Permission> list = permissionMapper.getAll();
        for (Permission permission : list) {
            filterChainDefinitionMap.put(permission.getResource(), "perms["+permission.getSn()+"]");
        }
        //需要登录访问的资源 , 一般将/**放在最下边
        filterChainDefinitionMap.put("/**", "anon");   //    , 不需要认证。
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }

permissionMapper

public interface PermissionMapper extends BaseMapper<Permission> {
    List<Permission> getPermissionsByUserName(@Param("username")String username);

    List<Permission> getAll();
}

Xml

    <select id="getAll" resultMap="BaseResultMap">
        select * from permission
    </select>

2.AuthRealm授权

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("Shiro开始授权操作");
        String username = SecurityUtils.getSubject().getPrincipal().toString();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        Set<String> roles=new HashSet<>();
        //得到一个用户的所有角色
        List<Role> rolesList = roleMapper.getRolesByUserName(username);
        for (Role role : rolesList) {
            roles.add(role.getRoleName());
        }
        //得到一个用户的所有权限,给当前用户授权
        List<Permission> permissionsList = permissionMapper.getPermissionsByUserName(username);
        for (Permission permission : permissionsList) {
            authorizationInfo.addStringPermission(permission.getSn());
        }
        authorizationInfo.setRoles(roles);
        return authorizationInfo;
    }

Mapper

public interface RoleMapper extends BaseMapper<Role> {
    List<Role> getRolesByUserName(@Param("username")String username);
}

Xml

    <select id="getRolesByUserName" resultMap="BaseResultMap">
        select
        r.id,
        r.role_name,
        r.remake
        from role r
        left join user_role ur on ur.role_id=r.id
        left join user u on u.id=ur.user_id
        where u.username=#{username}
    </select>

Xml

    <resultMap id="BaseResultMap" type="com.appointment.model.Permission">
        <id column="id" property="id" />
        <result column="permission_name" property="permissionName" />
        <result column="sn" property="sn" />
        <result column="resource" property="resource" />
        <result column="menu_id" property="menuId" />
    </resultMap>
    <select id="getPermissionsByUserName" resultMap="BaseResultMap">
        select
        p.id id,
        p.permission_name,
        p.resource resource,
        p.sn sn,
        p.menu_id
        from permission p
        left join role_permission rp on rp.permission_id=p.id
        left join role r on r.id=rp.role_id
        left join user_role ur on ur.role_id=r.id
        left join user u on u.id=ur.user_id
        where u.username=#{username}
    </select>

总结

参考表数据

menu

permission表

role_permission

role

role_user


user


前端页面效果


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容