目录
前言
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>
这种方式缺点是需要手写所有菜单标签,也要在shiro标签内加上对应的权限标识,在菜单列表比较多的时候,index页面会显得很臃肿,而且自己也容易混淆其中的权限标识。
配置数据库动态生成菜单栏
配置后台权限表,在用户登录通过后,进入index页面时,通过用户的角色id查询出该用户拥有的权限,在使用freemarker或者thymeleaf模板引擎动态地渲染出左侧菜单栏。在需要添加菜单栏时,只需要在数据库中添加数据就可以了。并且所有的权限数据都是在数据库中完成的,不需要再单独地写shiro标签了。
实现步骤
数据库表
1.user表
2.user_role表
3.role表
4.permission表
5.role_permission表
6.menu表
后台实现
-
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"; }
-
Service
public interface MenuService extends IService
-
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); }
}
-
Mapper
public interface MenuMapper extends BaseMapper
-
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>
-
VO对象
@Data
public class Menus {
private Long menuId;
private String menuName;
private String menuUrl;
private String menuIcon;
private List subMenus;
} -
index页面使用freemarker渲染菜单列表
//此处是通过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配置
-
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 {
List getPermissionsByUserName(@Param(“username”)String username);List<Permission> getAll();
}
-
Xml
<select id="getAll" resultMap="BaseResultMap"> select * from permission </select>
-
创建realm类
@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 {
List 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