权限分类
登录权限:没有登录,系统的任何资源都不能访问
菜单权限:根据用户的权限级别显示菜单,不同的人登录系统后看到的菜单列表个数是不一样的
资源权限:同样的资源按钮,A能点击,B不能点击
1.展示一级和二级权限
2.授权
3.动态菜单
4.登录
5.资源权限/访问权限
1.展示一级和二级权限思路分析
1.把权限表全部分一级和二级展示出来
2.通过前台传入的角色id,查询出当前角色拥有的权限并回显到前台
一级和二级权限效果展示
Permission权限domain
/**
* 权限domain
*/
public class Permission {
//权限主键id
private Long id;
//权限名
private String name;
//权限的url地址
private String url;
//parent_id代表父级权限,用Permission来单独表达父级权限的东西
private Permission parent;//这里写上只是为了和数据库表一致,parent_id都为null,写不写都可以
//前端用children遍历的二级权限,所有我们用children来封装二级权限
//创建一个list集合来装子权限
private List<Permission> children = new ArrayList<>();
省略所有的setter和getter方法...
}
PermissionMapper中查询所有的一级和二级权限
public interface PermissionMapper {
//查询一级和二级权限
List<Permission> selectOneLevelAndTwoLevelPermission();
}
PermissionMapper.xml中执行查询所有的一级和二级权限的SQL语句
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--权限相关的mapper-->
<mapper namespace="cn.itsource.auth.mapper.PermissionMapper">
<!-- 通过结果映射 查询一级和二级权限-->
<select id="selectOneLevelAndTwoLevelPermission" resultMap="resultPermissionMap">
SELECT p.*,c.id cid,c.name cname FROM permission p
LEFT JOIN permission c ON p.id=c.parent_id
WHERE p.parent_id IS NULL
</select>
<resultMap id="resultPermissionMap" type="Permission">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<result column="url" property="url"></result>
<collection property="children" ofType="Permission">
<id column="cid" property="id"></id>
<result column="cname" property="name"></result>
</collection>
</resultMap>
</mapper>
通过Permission表的自连接查询出一级权限和二级权限查询出的
(一级权限和二级权限表)和Permission的domain映射
把数据通过domain传给前台页面
下面数据,左侧都是一级权限,右侧都是二级权限
2.授权思路分析
1 前台传给我们角色id和权限id的集合
2.根据角色id删除角色和权限关联表 对应的所有数据
3.根据角色id和权限id在关联表中添加全部的数据(因为前面删完了)
角色表和权限表之间存在中间表
无论是添加还是删除,都要考虑中间表
否则会导致添加、删除失败
@Override
@Transactional
public void del(Long id) {
//删除角色和权限关联表中的数据
roleMapper.delRolePermission(id);
//删除角色对象
roleMapper.del(id);
}
/**
* 角色和权限绑定在一起
* @param roleId 角色id
* @param permissionIds 权限id
*/
@Override
@Transactional
public void addRolePermission(Long roleId, Long[] permissionIds) {
//先删除角色和权限的关联数据
roleMapper.delRolePermission(roleId);
for (Long permissionId : permissionIds) {
//角色和权限绑定在一起
roleMapper.addRoleidAndPermissionid(roleId, permissionId);
}
}
3.动态菜单思路分析
1.根据登录员工查询对应的一级和二级菜单
2 用到多表查询+关联结果映射
3.把数据交给前台回显
.动态菜单效果展示
Menu菜单domain代码
/**
* 菜单的domain
*/
public class Menu {
private Long id;
private String name;
private String url;
private String icon;
//一级菜单
private Menu parent;
//二级菜单,子菜单children是个集合
private List<Menu> children;
省略所有的setter和getter方法...
}
MenuMapper代码(根据员工id查询菜单,每个员工对应的菜单不一样)
public interface MenuMapper {
//通过员工id自连接表查询出对应的一级菜单和二级菜单
List<Menu> selectMenuByEmployeeId(Employee employee);
}
MenuMapper.xml(几张表多表查询+关联结果映射)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--菜单相关的mapper-->
<mapper namespace="cn.itsource.auth.mapper.MenuMapper">
<!--List<Menu> selectMenuByEmployeeId(Employee employee);//通过员工id自连接表查询出对应的一级菜单和二级菜单-->
<select id="selectMenuByEmployeeId" resultMap="resultMenuMap">
SELECT DISTINCT m.*,p.id pid,p.name pname,p.url purl from employee e
inner join employee_role er on e.id = er.employee_id
inner join role r on er.role_id = r.id
inner join role_permission rp on r.id = rp.role_id
inner join permission per on rp.permission_id = per.id
inner join menu m on per.menu_id = m.id
LEFT JOIN menu p on p.id = m.parent_id
where e.id = #{id}
</select>
<resultMap id="resultMenuMap" type="Menu">
<id column="pid" property="id"></id>
<result column="pname" property="name"></result>
<result column="purl" property="url"></result>
<collection property="children" ofType="Menu">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<result column="url" property="url"></result>
<result column="icon" property="icon"></result>
</collection>
</resultMap>
</mapper>
4.登录
1.自定义登录拦截器LoginInterceptor 拦截从未登录过的用户请求
2. 根据用户名去数据库查询对象
.对象不存在--登录失败
.对象存在–继续判断密码是否正确
3.如果密码正确,将对象保存进session里(一次会话)
4.将success = true的数据返回给前端,前端执行登录跳转
5. 资源权限理解
通过拦截器实现功能,公共资源不拦截,对特定资源进行拦截
什么是特定资源? 只要存放在Permission表(权限表)中的资源都是特定资源,都进行拦截
1.查询出权限表中所有的特定权限(url)
2.用户点击后判断其uri是否在特定权限内,不在特定权限内就放行
3.如果在特定权限内,就判断当前用户是否有访问特定资源的权限
4.有此权限就放行,权限不足就拦截请求并重定向到充值界面
自定义PermissionInterceptor 权限拦截器
/**
* 权限拦截器
*/
@Component //三层结构外的通用注解 //springboot自定义拦截器必须实现HandlerInterceptor 接口
public class PermissionInterceptor implements HandlerInterceptor {
@Autowired
private PermissionMapper permissionMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
//获取当前请求的uri地址
String uri = request.getRequestURI();
//获取permission中所有的数据(得到特定资源)
List<String> permissions = permissionMapper.selectPermissionurls();
//验证当前请求是否属于特定资源,如果是,则获取当前用户拥有的权限
if(permissions.contains(uri)){
HttpSession session = request.getSession();
//获取到登录用户
Employee employee = (Employee) session.getAttribute("USER_IN_SESSION");
//获取当前用户拥有哪些权限
List<String> myPermissions = permissionMapper.selectPermissionurlsByLoginEmployee(employee);
//如果拥有权限,则直接放行
if(myPermissions.contains(uri)){
return true;
}else{
//没有权限,跳转到没有权限的界面(单独使用一个controller来接收请求)
response.sendRedirect("/nopermission");
return false;
}
}
return true;
}
}
该类是一个配置类 就类似于以前的xml,权限拦截器在配置类中注册后才能起效
/**
* 自定义一个类,实现SpringMVC的配置类,覆盖addInterceptors方法
*/
@Configuration// 该类是一个配置类 就类似于以前的xml
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Autowired
private PermissionInterceptor permissionInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册登录拦截器
registry.addInterceptor(loginInterceptor).addPathPatterns("/**")
.excludePathPatterns("/login", "/assets/**","/nopermission");
//注册权限拦截器
registry.addInterceptor(permissionInterceptor).addPathPatterns("/**")
.excludePathPatterns("/login", "/assets/**","/nopermission");
}
/**
* 添加视图控制器(好处:避免重新创建Controller,避免资源浪费)
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//匹配/nopermission资源,一旦匹配成功,则跳转到nopermission的界面
registry.addViewController("/nopermission").setViewName("nopermission");
}
}