SpringBoot>15 - 自定义注解实现权限控制

简介:

较原始的项目中使用的是JSP页面标签结合后台方法上注解配合实现权限控制,目前前后端完全分离开发已经是主流模式,大型的项目中可能会用到 shiro 或者 spring security 做安全校验,但是在小型项目中可以使用自定义注解达到权限控制的要求。

误区:

很多人认为权限控制就是控制不同用户在页面上拥有不同的功能(即不同的按钮),这是用户体验范围,这种情况下,只要知道后端地址,还是可以请求到后端数据的。权限控制做的是没有权限就不可以操作数据,即安全问题。

数据库表设计:

1、用户表user,存放不同的用户
2、角色表role,不同的角色(管理员、普通用户…)
3、用户角色表user_role,存放用户和角色之间的关系
4、权限表permission、存放不同的权限(用户的增删改查)
5、角色权限表role_permission,存放不同角色和权限之间的关系
在这里插入图片描述
省略:使用插件生成对应的实体类、mapper、mapper.xml。

对应表的维护

controller service层的建立
1、用户表、代码很简单的CRUD操作,省略。
2、角色表的维护。

/**
* @Auther: xf
* @Date: 2018/11/24 21:35
* @Description: 角色表的维护
*/
@RequestMapping(value = "role")
@RestController
public class RoleController {

   @Autowired
   private RoleService roleService;

   @GetMapping(value={"list"})
   public ApiResult list() {
       List<Role> roleList = roleService.list();
       return ApiResult.ok(roleList);
   }

   @PostMapping(value="save")
   public ApiResult saver(@RequestBody Role role) {
       int result = roleService.save(role);
       return ApiResult.ok(result);
   }

   @GetMapping(value="get/{id}")
   public ApiResult getUser(@PathVariable Integer id) {
       Role role = roleService.get(id);
       return ApiResult.ok(role);
   }

   @PutMapping(value="update")
   public ApiResult putUser(@RequestBody Role role) {
       int result = roleService.update(role);
       return ApiResult.ok(result);
   }

   @DeleteMapping(value="delete/{id}")
   public ApiResult delete(@PathVariable Integer id) {
       int result = roleService.delete(id);
       return ApiResult.ok(result);
   }
}

3、权限表,这张表的维护很多项目都是开发人员直接维护,不暴露给用户。
4、用户角色表维护,CRUD,代码省略。

5、权限角色表维护
5.1、新建PermissionTree 实体 和 RolePermissionVo 实体

/**
 * @Auther: xf
 * @Date: 2018/11/24 22:13
 * @Description:  权限树
 */
@Data
public class PermissionTree extends Permission{
    private Boolean checked = false;  // 回显是否被选中
    private List<PermissionTree> children = Lists.newArrayList();  // 子集
}
/**
 * @Auther: xf
 * @Date: 2018/11/24 22:25
 * @Description:  修改角色-权限的时候使用
 */
@Data
public class RolePermissionVo {
    private String roleId;
    List<RolePermission> rolePermissionList = Lists.newArrayList();
}

5.2、一般将权限查询成一棵树,权限角色用于回显该角色拥有哪些权限。

/**
 * @Auther: xf
 * @Date: 2018/11/24 21:49
 * @Description:  权限
 */
@RestController
@RequestMapping(value = "permission")
public class PermissionController {
    @Autowired
    private PermissionService permissionService;
    /**
     * 权限树
     * @param roleId  角色id 用户查询回显信息
     * @return
     */
    @GetMapping(value = "getPermissionTree/{roleId}")
    public ApiResult getPermissionTree(@PathVariable Integer roleId) {
        List<PermissionTree> list = permissionService.getPermissionTree();
        return ApiResult.ok(list);
    }
    /**
     * 修改 角色--权限
     * @param rolePermissionVo
     * @return
     */
    @PostMapping(value = "updatePermission}")
    public ApiResult updatePermission(@RequestBody RolePermissionVo rolePermissionVo) {
        permissionService.updatePermission(rolePermissionVo);
        return ApiResult.ok();
    }
}

5.3、PermissionService层逻辑

/**
 * @Auther: xf
 * @Date: 2018/11/24 21:59
 * @Description: 权限树
 */
@Service
public class PermissionServiceImpl implements PermissionService {

    @Autowired
    private PermissionMapper permissionMapper;
    @Override
    public List<PermissionTree> getPermissionTree(Integer roleId) {
        // 存放查询结果
        List<PermissionTree> permissionList = Lists.newArrayList();
        // 查询所有的权限
        List<PermissionTree> permissions = permissionMapper.getAll();
        // role_permission 中选中的
        List<Integer> permissionIds = permissionMapper.getPermissionIds(roleId);
        // 中转容器  map 的索引查询可以提高效率
        Map<Integer, PermissionTree> permissionMap = new HashMap<>();
        for (PermissionTree p : permissions) {
            // 判断有没有选中
            if(permissionIds.contains(p.getId())){
                p.setChecked(true);
            }
            permissionMap.put(p.getId(), p);
        }
        for (PermissionTree p : permissions) {
            PermissionTree child = p;
            if(p.getPid() == 0){
                // 根节点
                permissionList.add(p);
            }else{
                PermissionTree parent = permissionMap.get(child.getPid());
                parent.getChildren().add(child);
            }
        }
        return permissionList;
    }

    // 事物处理
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updatePermission(RolePermissionVo rolePermissionVo) {
        // 1、根据roleId 删除所有的权限
        Integer roleId = rolePermissionVo.getRoleId();
        permissionMapper.deleteRolePermissionByRoleId(roleId);

        // 2. 新增
        List<RolePermission> rolePermissionList = rolePermissionVo.getRolePermissionList();
        if(rolePermissionList.size() > 0){
            for (RolePermission rolePermission : rolePermissionList) {
                // 单条更新
                permissionMapper.insertRolePermission(rolePermission);
            }
        }
    }
}

注意:权限树的查询方法,可以使用MyBatis自带的递归查询、java代码的递归查询、嵌套for循环等等,本章中使用的方法效率优于以上几种,推荐使用。

5.4、测试权限树
permission表 和 role_permission表如下:
在这里插入图片描述
在这里插入图片描述
注意: permission表最顶级权限的 pid 一定要是 0 在service 层中根据是否为 0 判断父级。
此处对应的权限为:roleId 为1 的角色拥有用户的全部权限和产品查询的权限。

请求接口:localhost:8080/permission/getPermissionTree/1
部分截图:
在这里插入图片描述

至此,以上配置,前端拿到json数据就可以根据不同的角色进行页面显示了,不过这并没有达到真正的权限控制,只是属于用户体验。

自定义注解验证权限:

1、新建注解类

/**
 * @Auther: xf
 * @Date: 2018/11/24 23:24
 * @Description: 自定义注解
 */
// 标注这个类它可以标注的位置
@Target({ElementType.METHOD, ElementType.TYPE})
// 标注这个注解的注解保留时期
@Retention(RetentionPolicy.RUNTIME)
// 是否生成注解文档
@Documented
public @interface RequiredPermission {
    String value();
}

2、新建权限拦截器

/**
 * @Auther: xf
 * @Date: 2018/11/24 23:26
 * @Description: 权限验证拦截器
 */
@Component
public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
    @Autowired
    private UserService userService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 防止 userService 注入不进来
        if (null == userService) {
            BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
            userService = (UserService) factory.getBean("userService");
        }
        // 验证权限
        if (this.hasPermission(handler)) {
            return true;
        }
        //  null == request.getHeader("x-requested-with") TODO 暂时用这个来判断是否为ajax请求
        // 如果没有权限 则抛403异常 springboot会处理,跳转到 /error/403 页面
        response.sendError(HttpStatus.FORBIDDEN.value(), "无权限");
        return false;
    }

    /**
     * 是否有权限
     */
    private boolean hasPermission(Object handler) {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 获取方法上的注解
            RequiredPermission requiredPermission = handlerMethod.getMethod().getAnnotation(RequiredPermission.class);
            // 如果方法上的注解为空 则获取类的注解
            if (requiredPermission == null) {
                requiredPermission = handlerMethod.getMethod().getDeclaringClass().getAnnotation(RequiredPermission.class);
            }
            // 如果注解为null, 说明不需要拦截, 直接放过
            if (requiredPermission == null) {
                return true;
            }
            // 如果标记了注解,则判断权限
            if (StringUtils.isNotBlank(requiredPermission.value())) {
                // 应该到 redis 或数据库 中获取该用户的权限信息 并判断是否有权限
                //Set<String> permissionSet = userService.getPermissionSet();
                // 这里测试使用 直接add
                Set<String> permissionSet  = new HashSet<>();
                permissionSet.add("user:view");
                permissionSet.add("user:save");

                if (CollectionUtils.isEmpty(permissionSet) ){
                    return false;
                }
                return permissionSet.contains(requiredPermission.value());
            }
        }
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}

3、权限拦截器的配置

@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
    /*
     * 拦截器配置
     * 在spring-mvc.xml配置文件内添加<mvc:interceptor>标签配置拦截器。
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        // 权限校验拦截器配置
        registry.addInterceptor(new AuthenticationInterceptor()).addPathPatterns("/**");

        // 父类的配置
        super.addInterceptors(registry);
    }
}

4、修改UserController,用户列表 和 删除的方法上加上自定义的注解

public class UserController {
    // 查询用户列表权限
    @RequiredPermission("user:view")
    @ApiOperation(value="获取用户列表", notes="")
    @RequestMapping(value={"getUserList"}, method= RequestMethod.GET)
    public ApiResult getUserList() {
        List<User> userList = userService.getAllUser();
        return ApiResult.ok(userList);
    }

    // 删除用户的权限
    @RequiredPermission("user:delete")
    @ApiOperation(value="删除用户", notes="根据url的id来指定删除对象")
    @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
    @RequestMapping(value="delete/{id}", method=RequestMethod.DELETE)
    public ApiResult deleteUser(@PathVariable Integer id) {
        int result = userService.deleteUser(id);
        return ApiResult.ok(result);
    }
}

分析: 该用户只有“user:view” 和 “user:save” 的权限,所以该用户不可以删除数据(需要"user:delete"权限)。
5、测试:
5.1、用户列表 :localhost:8080/user/getUserList 正常返回数据。
5.2、删除用户:localhost:8080/user/delete/1 抛出异常,没有权限。
在这里插入图片描述
至此,服务端权限配置完成。

个人学习分享
更多 springboot、springcloud、docker 文章关注微信公众号吧:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/cp026la/article/details/86506749